deepl-rb 3.6.1 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +8 -1
  3. data/CHANGELOG.md +35 -1
  4. data/Gemfile +0 -2
  5. data/README.md +165 -34
  6. data/Rakefile +2 -0
  7. data/VERSION +1 -1
  8. data/deepl-rb.gemspec +52 -20
  9. data/lib/deepl/requests/base.rb +16 -0
  10. data/lib/deepl/requests/document/upload.rb +6 -5
  11. data/lib/deepl/requests/rephrase.rb +3 -2
  12. data/lib/deepl/requests/style_rule/create.rb +46 -0
  13. data/lib/deepl/requests/style_rule/create_custom_instruction.rb +45 -0
  14. data/lib/deepl/requests/style_rule/destroy.rb +39 -0
  15. data/lib/deepl/requests/style_rule/destroy_custom_instruction.rb +40 -0
  16. data/lib/deepl/requests/style_rule/find.rb +40 -0
  17. data/lib/deepl/requests/style_rule/find_custom_instruction.rb +41 -0
  18. data/lib/deepl/requests/style_rule/update.rb +42 -0
  19. data/lib/deepl/requests/style_rule/update_configured_rules.rb +41 -0
  20. data/lib/deepl/requests/style_rule/update_custom_instruction.rb +47 -0
  21. data/lib/deepl/requests/translate.rb +17 -4
  22. data/lib/deepl/requests/translation_memory/list.rb +58 -0
  23. data/lib/deepl/resources/style_rule.rb +2 -1
  24. data/lib/deepl/resources/translation_memory.rb +25 -0
  25. data/lib/deepl/style_rule_api.rb +44 -0
  26. data/lib/deepl/translation_memory_api.rb +17 -0
  27. data/lib/deepl.rb +75 -55
  28. data/lib/version.rb +1 -1
  29. data/spec/api/deepl_spec.rb +134 -332
  30. data/spec/integration_tests/document_api_spec.rb +4 -18
  31. data/spec/integration_tests/document_error_paths_spec.rb +33 -0
  32. data/spec/integration_tests/glossary_api_spec.rb +114 -0
  33. data/spec/integration_tests/glossary_error_paths_spec.rb +107 -0
  34. data/spec/integration_tests/languages_api_spec.rb +54 -0
  35. data/spec/integration_tests/languages_error_paths_spec.rb +25 -0
  36. data/spec/integration_tests/rephrase_api_spec.rb +90 -0
  37. data/spec/integration_tests/rephrase_error_paths_spec.rb +53 -0
  38. data/spec/integration_tests/smoke_test_spec.rb +24 -0
  39. data/spec/integration_tests/style_rule_api_spec.rb +55 -17
  40. data/spec/integration_tests/style_rule_error_paths_spec.rb +45 -0
  41. data/spec/integration_tests/translate_api_spec.rb +98 -0
  42. data/spec/integration_tests/translate_error_paths_spec.rb +48 -0
  43. data/spec/integration_tests/translation_memory_api_spec.rb +54 -0
  44. data/spec/integration_tests/translation_memory_error_paths_spec.rb +19 -0
  45. data/spec/integration_tests/usage_api_spec.rb +29 -0
  46. data/spec/integration_tests/usage_error_paths_spec.rb +18 -0
  47. data/spec/requests/glossary/create_spec.rb +0 -21
  48. data/spec/requests/glossary/destroy_spec.rb +0 -39
  49. data/spec/requests/glossary/entries_spec.rb +0 -35
  50. data/spec/requests/glossary/find_spec.rb +0 -40
  51. data/spec/requests/glossary/language_pairs_spec.rb +0 -13
  52. data/spec/requests/glossary/list_spec.rb +0 -27
  53. data/spec/requests/languages_spec.rb +0 -41
  54. data/spec/requests/rephrase_spec.rb +13 -139
  55. data/spec/requests/style_rule/create_custom_instruction_spec.rb +30 -0
  56. data/spec/requests/style_rule/create_spec.rb +29 -0
  57. data/spec/requests/style_rule/destroy_custom_instruction_spec.rb +28 -0
  58. data/spec/requests/style_rule/destroy_spec.rb +27 -0
  59. data/spec/requests/style_rule/find_custom_instruction_spec.rb +29 -0
  60. data/spec/requests/style_rule/find_spec.rb +28 -0
  61. data/spec/requests/style_rule/list_spec.rb +27 -0
  62. data/spec/requests/style_rule/update_configured_rules_spec.rb +31 -0
  63. data/spec/requests/style_rule/update_custom_instruction_spec.rb +32 -0
  64. data/spec/requests/style_rule/update_spec.rb +29 -0
  65. data/spec/requests/translate_spec.rb +8 -218
  66. data/spec/requests/translation_memory/list_spec.rb +27 -0
  67. data/spec/requests/usage_spec.rb +0 -16
  68. data/spec/resources/custom_instruction_spec.rb +32 -0
  69. data/spec/resources/style_rule_spec.rb +68 -0
  70. data/spec/resources/translation_memory_spec.rb +35 -0
  71. data/spec/spec_helper.rb +15 -45
  72. data/spec/support/live_mock_server.rb +12 -0
  73. data/spec/support/managed_glossary.rb +17 -0
  74. data/spec/support/managed_style_rule.rb +17 -0
  75. data/spec/support/managed_translation_memory.rb +7 -0
  76. metadata +48 -19
  77. data/spec/fixtures/vcr_cassettes/deepl_document.yml +0 -95
  78. data/spec/fixtures/vcr_cassettes/deepl_document_download.yml +0 -1214
  79. data/spec/fixtures/vcr_cassettes/deepl_glossaries.yml +0 -1163
  80. data/spec/fixtures/vcr_cassettes/deepl_languages.yml +0 -54
  81. data/spec/fixtures/vcr_cassettes/deepl_rephrase.yml +0 -87
  82. data/spec/fixtures/vcr_cassettes/deepl_translate.yml +0 -358
  83. data/spec/fixtures/vcr_cassettes/deepl_usage.yml +0 -129
  84. data/spec/fixtures/vcr_cassettes/glossaries.yml +0 -1702
  85. data/spec/fixtures/vcr_cassettes/languages.yml +0 -229
  86. data/spec/fixtures/vcr_cassettes/rephrase_texts.yml +0 -401
  87. data/spec/fixtures/vcr_cassettes/style_rules.yml +0 -92
  88. data/spec/fixtures/vcr_cassettes/translate_texts.yml +0 -10630
  89. data/spec/fixtures/vcr_cassettes/usage.yml +0 -171
@@ -0,0 +1,114 @@
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 'spec_helper'
7
+
8
+ describe DeepL::GlossaryApi do
9
+ let(:default_glossary_args) do
10
+ {
11
+ name: 'Integration Test Glossary',
12
+ source_lang: 'en',
13
+ target_lang: 'de',
14
+ entries: [%w[Hello Hallo], %w[World Welt]]
15
+ }
16
+ end
17
+
18
+ describe 'happy path lifecycle' do
19
+ it 'creates, lists, and finds a glossary' do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations
20
+ with_managed_glossary(**default_glossary_args) do |glossary|
21
+ expect(glossary).to be_a(DeepL::Resources::Glossary)
22
+ expect(glossary.id).to be_a(String)
23
+ expect(glossary.name).to eq(default_glossary_args[:name])
24
+ expect(glossary.entry_count).to eq(2)
25
+ expect(glossary.source_lang).to eq(default_glossary_args[:source_lang])
26
+ expect(glossary.target_lang).to eq(default_glossary_args[:target_lang])
27
+ expect(glossary.ready).to be(true).or be(false)
28
+ expect { Time.iso8601(glossary.creation_time) }.not_to raise_error
29
+
30
+ listed = DeepL.glossaries.list
31
+ expect(listed).to be_an(Array)
32
+ expect(listed.map(&:id)).to include(glossary.id)
33
+
34
+ found = DeepL.glossaries.find(glossary.id)
35
+ expect(found).to be_a(DeepL::Resources::Glossary)
36
+ expect(found.id).to eq(glossary.id)
37
+ expect(found.name).to eq(glossary.name)
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#language_pairs' do
43
+ it 'returns a non-empty list of supported source/target combos' do
44
+ pairs = DeepL.glossaries.language_pairs
45
+ expect(pairs).to be_an(Array)
46
+ expect(pairs).not_to be_empty
47
+ expect(pairs.first).to be_a(DeepL::Resources::LanguagePair)
48
+ expect(pairs.first.source_lang).to be_a(String)
49
+ expect(pairs.first.target_lang).to be_a(String)
50
+ end
51
+ end
52
+
53
+ describe 'error handling' do
54
+ it 'raises when creating a glossary with an invalid source language code' do
55
+ expect do
56
+ DeepL.glossaries.create(
57
+ 'Invalid Lang Glossary',
58
+ 'zz',
59
+ 'de',
60
+ [%w[Hello Hallo]]
61
+ )
62
+ end.to raise_error(DeepL::Exceptions::RequestError)
63
+ end
64
+ end
65
+
66
+ describe '#destroy' do
67
+ it 'returns the id of the destroyed glossary' do
68
+ glossary = DeepL.glossaries.create(
69
+ default_glossary_args[:name],
70
+ default_glossary_args[:source_lang],
71
+ default_glossary_args[:target_lang],
72
+ default_glossary_args[:entries]
73
+ )
74
+
75
+ expect(DeepL.glossaries.destroy(glossary.id)).to eq(glossary.id)
76
+ end
77
+ end
78
+
79
+ describe '#entries' do
80
+ it 'returns the glossary entries as an array of [source, target] pairs' do # rubocop:disable RSpec/ExampleLength
81
+ with_managed_glossary(**default_glossary_args) do |glossary|
82
+ entries = DeepL.glossaries.entries(glossary.id)
83
+
84
+ expect(entries).to be_an(Array)
85
+ expect(entries).to all(be_an(Array))
86
+ expect(entries.size).to eq(default_glossary_args[:entries].size)
87
+ entries.each do |entry|
88
+ expect(entry.size).to eq(2)
89
+ expect(entry.first).to be_a(String)
90
+ expect(entry.last).to be_a(String)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ describe 'edge cases' do
97
+ it 'accepts Unicode keys and values in glossary entries' do # rubocop:disable RSpec/ExampleLength
98
+ unicode_entries = [
99
+ ['Schöne Grüße', 'Best regards'],
100
+ ['🚀 rocket', '🚀 fusée'],
101
+ ['日本語', 'Japanese'],
102
+ ['café', 'kafē']
103
+ ]
104
+ args = default_glossary_args.merge(
105
+ name: 'Unicode Glossary',
106
+ entries: unicode_entries
107
+ )
108
+
109
+ with_managed_glossary(**args) do |glossary|
110
+ expect(glossary.entry_count).to eq(unicode_entries.length)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,107 @@
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 'spec_helper'
7
+
8
+ describe DeepL::GlossaryApi do # rubocop:disable RSpec/SpecFilePathFormat
9
+ include_context 'with a live mock server'
10
+
11
+ let(:nonexistent_glossary_id) { '00000000-0000-0000-0000-000000000000' }
12
+
13
+ let(:valid_glossary_args) do
14
+ {
15
+ name: 'Error Path Test Glossary',
16
+ source_lang: 'en',
17
+ target_lang: 'de',
18
+ entries: [%w[Hello Hallo]]
19
+ }
20
+ end
21
+
22
+ describe 'AuthorizationFailed (401/403)' do
23
+ let(:unauthorized_glossaries) { described_class.new(unauthorized_api) }
24
+
25
+ it 'is raised by #list when the auth key is invalid' do
26
+ expect { unauthorized_glossaries.list }
27
+ .to raise_error(DeepL::Exceptions::AuthorizationFailed)
28
+ end
29
+
30
+ it 'is raised by #language_pairs when the auth key is invalid' do
31
+ expect { unauthorized_glossaries.language_pairs }
32
+ .to raise_error(DeepL::Exceptions::AuthorizationFailed)
33
+ end
34
+
35
+ it 'is raised by #create when the auth key is invalid' do
36
+ expect do
37
+ unauthorized_glossaries.create(
38
+ valid_glossary_args[:name],
39
+ valid_glossary_args[:source_lang],
40
+ valid_glossary_args[:target_lang],
41
+ valid_glossary_args[:entries]
42
+ )
43
+ end.to raise_error(DeepL::Exceptions::AuthorizationFailed)
44
+ end
45
+
46
+ it 'is raised by #find when the auth key is invalid' do
47
+ expect { unauthorized_glossaries.find(nonexistent_glossary_id) }
48
+ .to raise_error(DeepL::Exceptions::AuthorizationFailed)
49
+ end
50
+
51
+ it 'is raised by #destroy when the auth key is invalid' do
52
+ expect { unauthorized_glossaries.destroy(nonexistent_glossary_id) }
53
+ .to raise_error(DeepL::Exceptions::AuthorizationFailed)
54
+ end
55
+ end
56
+
57
+ describe 'NotFound (404)' do
58
+ it 'is raised by #find for a well-formed but unknown glossary id' do
59
+ expect { DeepL.glossaries.find(nonexistent_glossary_id) }
60
+ .to raise_error(DeepL::Exceptions::NotFound)
61
+ end
62
+
63
+ it 'is raised by #destroy for a well-formed but unknown glossary id' do
64
+ expect { DeepL.glossaries.destroy(nonexistent_glossary_id) }
65
+ .to raise_error(DeepL::Exceptions::NotFound)
66
+ end
67
+ end
68
+
69
+ describe 'BadRequest (400) from #create' do
70
+ it 'is raised for an invalid source language code' do
71
+ expect do
72
+ DeepL.glossaries.create(
73
+ valid_glossary_args[:name],
74
+ 'zz',
75
+ valid_glossary_args[:target_lang],
76
+ valid_glossary_args[:entries]
77
+ )
78
+ end.to raise_error(DeepL::Exceptions::BadRequest)
79
+ end
80
+ end
81
+
82
+ describe 'BadRequest (400) from a malformed glossary id' do
83
+ let(:malformed_uuid) { 'invalid-uuid' }
84
+
85
+ it 'is raised by #find for a malformed glossary id' do
86
+ expect { DeepL.glossaries.find(malformed_uuid) }
87
+ .to raise_error(DeepL::Exceptions::BadRequest)
88
+ end
89
+
90
+ it 'is raised by #destroy for a malformed glossary id' do
91
+ expect { DeepL.glossaries.destroy(malformed_uuid) }
92
+ .to raise_error(DeepL::Exceptions::BadRequest)
93
+ end
94
+
95
+ it 'is raised by #entries for a malformed glossary id' do
96
+ expect { DeepL.glossaries.entries(malformed_uuid) }
97
+ .to raise_error(DeepL::Exceptions::BadRequest)
98
+ end
99
+ end
100
+
101
+ describe 'NotFound (404) from #entries' do
102
+ it 'is raised for a well-formed but unknown glossary id' do
103
+ expect { DeepL.glossaries.entries(nonexistent_glossary_id) }
104
+ .to raise_error(DeepL::Exceptions::NotFound)
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,54 @@
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 'spec_helper'
7
+
8
+ # Covers GET /v2/languages; the SDK has no v3 languages endpoint.
9
+ describe 'DeepL.languages' do # rubocop:disable RSpec/DescribeClass
10
+ describe 'source languages' do
11
+ it 'returns a non-empty array of Language resources with common codes' do
12
+ languages = DeepL.languages
13
+
14
+ expect(languages).to be_an(Array)
15
+ expect(languages).not_to be_empty
16
+ expect(languages).to all(be_a(DeepL::Resources::Language))
17
+ expect(languages.first).to respond_to(:code, :name)
18
+
19
+ codes = languages.map(&:code)
20
+ expect(codes).to include('EN', 'DE')
21
+ end
22
+ end
23
+
24
+ describe 'target languages' do
25
+ it 'returns target entries that include both formality-supporting and non-supporting members' do
26
+ languages = DeepL.languages(type: :target)
27
+
28
+ expect(languages).to be_an(Array)
29
+ expect(languages).not_to be_empty
30
+ expect(languages).to all(be_a(DeepL::Resources::Language))
31
+
32
+ formalities = languages.map(&:supports_formality?)
33
+ expect(formalities).to include(true)
34
+ expect(formalities).to include(false)
35
+ end
36
+
37
+ it 'raises NotSupported when supports_formality? is called on a source-language entry' do
38
+ source_languages = DeepL.languages
39
+
40
+ expect { source_languages.first.supports_formality? }
41
+ .to raise_error(DeepL::Exceptions::NotSupported)
42
+ end
43
+ end
44
+
45
+ describe 'source vs target counts' do
46
+ it 'returns sensible (>20) counts for both source and target listings' do
47
+ source_languages = DeepL.languages
48
+ target_languages = DeepL.languages(type: :target)
49
+
50
+ expect(source_languages.size).to be > 20
51
+ expect(target_languages.size).to be > 20
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
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 'spec_helper'
7
+
8
+ describe 'DeepL.languages error paths' do # rubocop:disable RSpec/DescribeClass
9
+ include_context 'with a live mock server'
10
+
11
+ describe 'authorization failures' do
12
+ it 'raises AuthorizationFailed when the auth key is invalid' do
13
+ request = DeepL::Requests::Languages.new(unauthorized_api)
14
+
15
+ expect { request.request }.to raise_error(DeepL::Exceptions::AuthorizationFailed)
16
+ end
17
+ end
18
+
19
+ describe 'bad request errors' do
20
+ it 'raises BadRequest for an unsupported languages type' do
21
+ expect { DeepL.languages(type: :invalid) }
22
+ .to raise_error(DeepL::Exceptions::BadRequest)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,90 @@
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 'spec_helper'
7
+
8
+ describe 'DeepL.rephrase' do # rubocop:disable RSpec/DescribeClass
9
+ describe 'happy path' do
10
+ it 'rephrases an English sentence into English and returns a Text resource' do
11
+ result = DeepL.rephrase('As Gregor Samsa awoke one morning he found himself transformed.',
12
+ 'en')
13
+
14
+ expect(result).to be_a(DeepL::Resources::Text)
15
+ expect(result.text).to be_a(String)
16
+ expect(result.text).not_to be_empty
17
+ end
18
+ end
19
+
20
+ describe 'error path' do
21
+ it 'raises a bad request error for an invalid writing_style value' do
22
+ expect do
23
+ DeepL.rephrase('As Gregor Samsa awoke one morning he found himself transformed.',
24
+ 'en', 'totally_invalid_style')
25
+ end.to raise_error(DeepL::Exceptions::BadRequest)
26
+ end
27
+ end
28
+
29
+ describe 'edge case' do
30
+ it 'rephrases with a writing_style option applied' do
31
+ result = DeepL.rephrase('As Gregor Samsa awoke one morning he found himself transformed.',
32
+ 'en', 'business')
33
+
34
+ expect(result).to be_a(DeepL::Resources::Text)
35
+ expect(result.text).to be_a(String)
36
+ expect(result.text).not_to be_empty
37
+ end
38
+
39
+ it 'rephrases with a tone option applied' do
40
+ result = DeepL.rephrase('As Gregor Samsa awoke one morning he found himself transformed.',
41
+ 'en', nil, 'friendly')
42
+
43
+ expect(result).to be_a(DeepL::Resources::Text)
44
+ expect(result.text).to be_a(String)
45
+ expect(result.text).not_to be_empty
46
+ end
47
+
48
+ it 'rephrases an array of texts with a tone applied' do
49
+ texts = [
50
+ 'As Gregor Samsa awoke one morning he found himself transformed.',
51
+ 'He lay on his armour-like back, and if he lifted his head a little.'
52
+ ]
53
+
54
+ results = DeepL.rephrase(texts, 'en', nil, 'friendly')
55
+
56
+ expect(results).to be_an(Array)
57
+ expect(results.size).to eq(2)
58
+ expect(results).to all(be_a(DeepL::Resources::Text))
59
+ expect(results.map(&:text)).to all(be_a(String).and(satisfy { |t| !t.empty? }))
60
+ end
61
+
62
+ it 'rephrases an array of texts with a writing_style applied' do
63
+ texts = [
64
+ 'As Gregor Samsa awoke one morning he found himself transformed.',
65
+ 'He lay on his armour-like back, and if he lifted his head a little.'
66
+ ]
67
+
68
+ results = DeepL.rephrase(texts, 'en', 'business')
69
+
70
+ expect(results).to be_an(Array)
71
+ expect(results.size).to eq(2)
72
+ expect(results).to all(be_a(DeepL::Resources::Text))
73
+ expect(results.map(&:text)).to all(be_a(String).and(satisfy { |t| !t.empty? }))
74
+ end
75
+
76
+ it 'rephrases an array of texts and returns an array of Text resources' do
77
+ texts = [
78
+ 'As Gregor Samsa awoke one morning he found himself transformed.',
79
+ 'He lay on his armour-like back, and if he lifted his head a little.'
80
+ ]
81
+
82
+ results = DeepL.rephrase(texts, 'en')
83
+
84
+ expect(results).to be_an(Array)
85
+ expect(results.size).to eq(2)
86
+ expect(results).to all(be_a(DeepL::Resources::Text))
87
+ expect(results.map(&:text)).to all(be_a(String).and(satisfy { |t| !t.empty? }))
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,53 @@
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 'securerandom'
7
+ require 'spec_helper'
8
+
9
+ describe 'DeepL.rephrase error paths' do # rubocop:disable RSpec/DescribeClass
10
+ include IntegrationTestUtils
11
+
12
+ include_context 'with a live mock server'
13
+
14
+ let(:sample_text) { 'As Gregor Samsa awoke one morning he found himself transformed.' }
15
+
16
+ def rephrase_with_fresh_user(headers)
17
+ fresh_auth_key = "rephrase-err-#{SecureRandom.uuid}"
18
+ config = DeepL::Configuration.new(auth_key: fresh_auth_key)
19
+ config.max_network_retries = 0
20
+ api = DeepL::API.new(config)
21
+ DeepL::Requests::Rephrase.new(api, sample_text, 'en', nil, nil, {}, headers).request
22
+ end
23
+
24
+ describe 'authorization failures' do
25
+ it 'raises AuthorizationFailed when called with an invalid auth_key' do
26
+ request = DeepL::Requests::Rephrase.new(unauthorized_api, sample_text, 'en')
27
+
28
+ expect { request.request }.to raise_error(DeepL::Exceptions::AuthorizationFailed)
29
+ end
30
+ end
31
+
32
+ describe 'bad request errors' do
33
+ it 'raises BadRequest for an unsupported target_lang code' do
34
+ expect do
35
+ DeepL.rephrase(sample_text, 'zzz')
36
+ end.to raise_error(DeepL::Exceptions::BadRequest)
37
+ end
38
+
39
+ it 'raises BadRequest when both writing_style and tone are provided' do
40
+ expect do
41
+ DeepL.rephrase(sample_text, 'en', 'business', 'friendly')
42
+ end.to raise_error(DeepL::Exceptions::BadRequest)
43
+ end
44
+ end
45
+
46
+ describe 'rate limiting' do
47
+ it 'raises LimitExceeded when the server responds with 429', :mock_server_only do
48
+ expect do
49
+ rephrase_with_fresh_user(respond_with_429_header(1))
50
+ end.to raise_error(DeepL::Exceptions::LimitExceeded)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright 2025 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 'spec_helper'
7
+
8
+ describe 'SDK smoke test', :integration do # rubocop:disable RSpec/DescribeClass
9
+ it 'exercises translate, glossaries.list, and usage end-to-end' do # rubocop:disable RSpec/ExampleLength
10
+ source_text = 'Hello, world!'
11
+ result = DeepL.translate(source_text, 'EN', 'DE')
12
+ expect(result).to be_a(DeepL::Resources::Text)
13
+ expect(result.text).to be_a(String)
14
+ expect(result.text).not_to be_empty
15
+ expect(result.text).not_to eq(source_text)
16
+
17
+ glossaries = DeepL.glossaries.list
18
+ expect(glossaries).to be_an(Array)
19
+
20
+ usage = DeepL.usage
21
+ expect(usage).to be_a(DeepL::Resources::Usage)
22
+ expect(usage).to respond_to(:character_count, :character_limit)
23
+ end
24
+ end
@@ -5,21 +5,9 @@
5
5
 
6
6
  require 'spec_helper'
7
7
 
8
- describe DeepL::StyleRuleApi do
9
- before do
10
- VCR.turn_off!
11
- WebMock.allow_net_connect!
12
- end
13
-
14
- after do
15
- VCR.turn_on!
16
- WebMock.disable_net_connect!
17
- end
18
-
8
+ describe DeepL::StyleRuleApi, :mock_server_only do
19
9
  describe '#translate_with_style_rules' do
20
10
  it 'when performing a request with style_id' do
21
- skip 'Only runs on mock server' if real_server?
22
-
23
11
  source_lang = 'DE'
24
12
  target_lang = 'EN-US'
25
13
  text = 'Hallo, Welt!'
@@ -30,8 +18,6 @@ describe DeepL::StyleRuleApi do
30
18
  end
31
19
 
32
20
  it 'when performing a request with style_rule object' do
33
- skip 'Only runs on mock server' if real_server?
34
-
35
21
  source_lang = 'DE'
36
22
  target_lang = 'EN-US'
37
23
  text = 'Hallo, Welt!'
@@ -44,7 +30,6 @@ describe DeepL::StyleRuleApi do
44
30
 
45
31
  describe '#list_style_rules' do
46
32
  it 'when requesting a list of all style rules' do
47
- skip 'Only runs on mock server' if real_server?
48
33
  style_rules = DeepL.style_rules.list(page: 0, page_size: 10, detailed: true)
49
34
  expect(style_rules).to be_an(Array)
50
35
  expect(style_rules.length).to eq(1)
@@ -55,7 +40,6 @@ describe DeepL::StyleRuleApi do
55
40
  end
56
41
 
57
42
  it 'when requesting a list of all style rules without detailed' do
58
- skip 'Only runs on mock server' if real_server?
59
43
  style_rules = DeepL.style_rules.list
60
44
  expect(style_rules).to be_an(Array)
61
45
  expect(style_rules.length).to eq(1)
@@ -65,6 +49,60 @@ describe DeepL::StyleRuleApi do
65
49
  end
66
50
  end
67
51
 
52
+ describe 'style rule management operations' do
53
+ it 'when performing all management operations on style rules' do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations
54
+ # Create a style rule
55
+ style_rule = DeepL.style_rules.create('Test Style Rule', 'en')
56
+ expect(style_rule).to be_a(DeepL::Resources::StyleRule)
57
+ expect(style_rule.style_id).not_to be_nil
58
+ style_id = style_rule.style_id
59
+
60
+ # Find the style rule
61
+ found_rule = DeepL.style_rules.find(style_id)
62
+ expect(found_rule).to be_a(DeepL::Resources::StyleRule)
63
+ expect(found_rule.style_id).to eq(style_id)
64
+
65
+ # Update the style rule name
66
+ updated_rule = DeepL.style_rules.update_name(style_id, 'Updated Style Rule')
67
+ expect(updated_rule).to be_a(DeepL::Resources::StyleRule)
68
+
69
+ # Update configured rules
70
+ configured_rules = {
71
+ 'dates_and_times' => { 'calendar_era' => 'use_bc_and_ad' }
72
+ }
73
+ updated_rule = DeepL.style_rules.update_configured_rules(style_id, configured_rules)
74
+ expect(updated_rule).to be_a(DeepL::Resources::StyleRule)
75
+
76
+ # Create a custom instruction
77
+ custom_instruction = DeepL.style_rules.create_custom_instruction(
78
+ style_id, 'Test Instruction', 'Always use formal language'
79
+ )
80
+ expect(custom_instruction).to be_a(DeepL::Resources::CustomInstruction)
81
+ expect(custom_instruction.id).not_to be_nil
82
+ instruction_id = custom_instruction.id
83
+
84
+ # Find a custom instruction
85
+ fetched_instruction = DeepL.style_rules.find_custom_instruction(style_id, instruction_id)
86
+ expect(fetched_instruction).to be_a(DeepL::Resources::CustomInstruction)
87
+ expect(fetched_instruction.id).to eq(instruction_id)
88
+
89
+ # Update a custom instruction
90
+ updated_instruction = DeepL.style_rules.update_custom_instruction(
91
+ style_id, instruction_id, 'Updated Instruction', 'Use casual language'
92
+ )
93
+ expect(updated_instruction).to be_a(DeepL::Resources::CustomInstruction)
94
+
95
+ # Destroy the custom instruction
96
+ deleted_instruction_id = DeepL.style_rules.destroy_custom_instruction(style_id,
97
+ instruction_id)
98
+ expect(deleted_instruction_id).to eq(instruction_id)
99
+
100
+ # Destroy the style rule
101
+ deleted_style_id = DeepL.style_rules.destroy(style_id)
102
+ expect(deleted_style_id).to eq(style_id)
103
+ end
104
+ end
105
+
68
106
  def build_test_style_rule
69
107
  style_rule_data = {
70
108
  'style_id' => 'dca2e053-8ae5-45e6-a0d2-881156e7f4e4',
@@ -0,0 +1,45 @@
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::StyleRuleApi, :mock_server_only do # rubocop:disable RSpec/SpecFilePathFormat
9
+ include_context 'with a live mock server'
10
+
11
+ let(:missing_uuid) { '00000000-0000-0000-0000-000000000000' }
12
+
13
+ describe 'authorization failures' do
14
+ let(:unauthorized_style_rules) { described_class.new(unauthorized_api) }
15
+
16
+ it 'raises AuthorizationFailed on a read operation (list)' do
17
+ expect { unauthorized_style_rules.list }
18
+ .to raise_error(DeepL::Exceptions::AuthorizationFailed)
19
+ end
20
+
21
+ it 'raises AuthorizationFailed on a write operation (create)' do
22
+ expect { unauthorized_style_rules.create('Auth Failure Test', 'en') }
23
+ .to raise_error(DeepL::Exceptions::AuthorizationFailed)
24
+ end
25
+ end
26
+
27
+ describe 'not-found failures' do
28
+ it 'raises NotFound when #find is called with a missing UUID' do
29
+ expect { DeepL.style_rules.find(missing_uuid) }
30
+ .to raise_error(DeepL::Exceptions::NotFound)
31
+ end
32
+ end
33
+
34
+ describe 'bad-request failures' do
35
+ it 'raises BadRequest when create is called with a nil language' do
36
+ expect { DeepL.style_rules.create('Nil Lang', nil) }
37
+ .to raise_error(DeepL::Exceptions::BadRequest)
38
+ end
39
+
40
+ it 'raises an error when #destroy is called with a malformed UUID' do
41
+ expect { DeepL.style_rules.destroy('invalid-uuid') }
42
+ .to raise_error(DeepL::Exceptions::Error)
43
+ end
44
+ end
45
+ end