letter_opener_web_wally 2.0.1
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 +7 -0
- data/.github/workflows/brakeman-analysis.yml +36 -0
- data/.github/workflows/main.yml +29 -0
- data/.github/workflows/release-gem.yml +32 -0
- data/.gitignore +20 -0
- data/.rspec +4 -0
- data/.rubocop.yml +29 -0
- data/.rubocop_todo.yml +19 -0
- data/CHANGELOG.md +95 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +0 -0
- data/README.md +176 -0
- data/Rakefile +10 -0
- data/app/controllers/letter_opener_web/application_controller.rb +7 -0
- data/app/controllers/letter_opener_web/letters_controller.rb +77 -0
- data/app/models/letter_opener_web/aws_letter.rb +98 -0
- data/app/models/letter_opener_web/base_letter.rb +93 -0
- data/app/models/letter_opener_web/letter.rb +54 -0
- data/app/models/letter_opener_web/s3_message.rb +25 -0
- data/app/views/layouts/letter_opener_web/_javascripts.html.erb +31 -0
- data/app/views/layouts/letter_opener_web/_styles.html.erb +3 -0
- data/app/views/layouts/letter_opener_web/js/_favcount.html.erb +104 -0
- data/app/views/layouts/letter_opener_web/js/_jquery.html.erb +7 -0
- data/app/views/layouts/letter_opener_web/letters.html.erb +15 -0
- data/app/views/layouts/letter_opener_web/styles/_bootstrap.html.erb +9 -0
- data/app/views/layouts/letter_opener_web/styles/_icon.html.erb +2 -0
- data/app/views/layouts/letter_opener_web/styles/_letters.html.erb +70 -0
- data/app/views/letter_opener_web/letters/_item.html.erb +10 -0
- data/app/views/letter_opener_web/letters/index.html.erb +24 -0
- data/bin/setup +6 -0
- data/config/routes.rb +9 -0
- data/letter_opener_web.gemspec +35 -0
- data/lib/letter_opener_web/delivery_method.rb +29 -0
- data/lib/letter_opener_web/engine.rb +20 -0
- data/lib/letter_opener_web/version.rb +5 -0
- data/lib/letter_opener_web.rb +52 -0
- data/script/pre-push +2 -0
- data/spec/controllers/letter_opener_web/letters_controller_spec.rb +282 -0
- data/spec/dummy/Rakefile +8 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +6 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +6 -0
- data/spec/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +4 -0
- data/spec/dummy/app/javascript/packs/application.js +15 -0
- data/spec/dummy/app/jobs/application_job.rb +9 -0
- data/spec/dummy/app/mailers/application_mailer.rb +6 -0
- data/spec/dummy/app/models/application_record.rb +5 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +15 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +33 -0
- data/spec/dummy/config/application.rb +37 -0
- data/spec/dummy/config/boot.rb +7 -0
- data/spec/dummy/config/environment.rb +7 -0
- data/spec/dummy/config/environments/development.rb +78 -0
- data/spec/dummy/config/environments/production.rb +122 -0
- data/spec/dummy/config/environments/test.rb +61 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +10 -0
- data/spec/dummy/config/initializers/assets.rb +14 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +10 -0
- data/spec/dummy/config/initializers/content_security_policy.rb +30 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +8 -0
- data/spec/dummy/config/initializers/inflections.rb +18 -0
- data/spec/dummy/config/initializers/mime_types.rb +6 -0
- data/spec/dummy/config/initializers/permissions_policy.rb +13 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +16 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/puma.rb +45 -0
- data/spec/dummy/config/routes.rb +5 -0
- data/spec/dummy/config.ru +8 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/storage/.keep +0 -0
- data/spec/letter_opener_web_spec.rb +47 -0
- data/spec/models/letter_opener_web/aws_letter_spec.rb +196 -0
- data/spec/models/letter_opener_web/letter_spec.rb +184 -0
- data/spec/rails_helper.rb +8 -0
- data/spec/spec_helper.rb +8 -0
- metadata +291 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
.rails-default-error-page {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
.rails-default-error-page div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
.rails-default-error-page div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
.rails-default-error-page h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
.rails-default-error-page div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body class="rails-default-error-page">
|
58
|
+
<!-- This file lives in public/500.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>We're sorry, but something went wrong.</h1>
|
62
|
+
</div>
|
63
|
+
<p>If you are the application owner check the logs for more information.</p>
|
64
|
+
</div>
|
65
|
+
</body>
|
66
|
+
</html>
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe LetterOpenerWeb do
|
6
|
+
subject { described_class }
|
7
|
+
after(:each) { described_class.reset! }
|
8
|
+
|
9
|
+
describe '.config' do
|
10
|
+
it 'sets default letters_storage' do
|
11
|
+
expect(subject.config.letters_storage).to eq(:local)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'sets default letters_location' do
|
15
|
+
expect(subject.config.letters_location).to eq(Rails.root.join('tmp', 'letter_opener'))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.configure' do
|
20
|
+
it 'yields config to the block' do
|
21
|
+
subject.configure do |config|
|
22
|
+
expect(config).to eq(subject.config)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'retains settings set within the block' do
|
27
|
+
subject.configure do |config|
|
28
|
+
config.letters_location = 'tmp/test_path'
|
29
|
+
end
|
30
|
+
|
31
|
+
expect(subject.config.letters_location).to eq('tmp/test_path')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '.reset!' do
|
36
|
+
it 'resets configuration' do
|
37
|
+
subject.configure do |config|
|
38
|
+
config.letters_location = 'tmp/test_path'
|
39
|
+
end
|
40
|
+
|
41
|
+
subject.reset!
|
42
|
+
|
43
|
+
expected = Rails.root.join('tmp', 'letter_opener')
|
44
|
+
expect(subject.config.letters_location).to eq(expected)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe LetterOpenerWeb::AwsLetter do
|
4
|
+
subject { described_class.new(id: letter_id) }
|
5
|
+
|
6
|
+
let(:aws_access_key_id) { 'aws_access_key_id' }
|
7
|
+
let(:aws_secret_access_key) { 'aws_secret_access_key' }
|
8
|
+
let(:aws_region) { 'aws_region' }
|
9
|
+
let(:aws_bucket) { 'aws_bucket' }
|
10
|
+
let(:letter_id) { 'letter_id' }
|
11
|
+
let(:letters_location) { nil }
|
12
|
+
|
13
|
+
let(:s3_client) do
|
14
|
+
instance_double(Aws::S3::Client)
|
15
|
+
end
|
16
|
+
|
17
|
+
before :each do
|
18
|
+
LetterOpenerWeb.configure do |config|
|
19
|
+
config.letters_storage = :s3
|
20
|
+
config.letters_location = letters_location
|
21
|
+
config.aws_access_key_id = aws_access_key_id
|
22
|
+
config.aws_secret_access_key = aws_secret_access_key
|
23
|
+
config.aws_region = aws_region
|
24
|
+
config.aws_bucket = aws_bucket
|
25
|
+
end
|
26
|
+
|
27
|
+
allow(Aws::S3::Client)
|
28
|
+
.to receive(:new)
|
29
|
+
.with(
|
30
|
+
access_key_id: aws_access_key_id,
|
31
|
+
secret_access_key: aws_secret_access_key,
|
32
|
+
region: aws_region
|
33
|
+
)
|
34
|
+
.and_return(s3_client)
|
35
|
+
end
|
36
|
+
|
37
|
+
after :each do
|
38
|
+
LetterOpenerWeb.reset!
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#letters_location' do
|
42
|
+
subject { described_class.letters_location }
|
43
|
+
|
44
|
+
context 'with a nil given value in LetterOpenerWeb.configure' do
|
45
|
+
let(:letters_location) { nil }
|
46
|
+
|
47
|
+
it { is_expected.to eq(nil) }
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with any value in LetterOpenerWeb.configure' do
|
51
|
+
let(:letters_location) { 'letters_location' }
|
52
|
+
|
53
|
+
it { is_expected.to eq(letters_location) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#search' do
|
58
|
+
it 'returns all letters', aggregates_failure: true do
|
59
|
+
expect(s3_client)
|
60
|
+
.to receive(:list_objects_v2)
|
61
|
+
.with(bucket: aws_bucket, prefix: '', delimiter: '.html')
|
62
|
+
.and_return(
|
63
|
+
instance_double(Aws::S3::Types::ListObjectsV2Output, common_prefixes: [
|
64
|
+
instance_double(Aws::S3::Types::CommonPrefix,
|
65
|
+
prefix: 'location/to/letters/1111_1111/toto.html'),
|
66
|
+
instance_double(Aws::S3::Types::CommonPrefix, prefix: '2222_2222/toto.html')
|
67
|
+
])
|
68
|
+
)
|
69
|
+
|
70
|
+
letters = described_class.search
|
71
|
+
|
72
|
+
expect(letters.size).to eq(2)
|
73
|
+
expect(letters.first.id).to eq('2222_2222')
|
74
|
+
expect(letters.last.id).to eq('1111_1111')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#destroy_all' do
|
79
|
+
let(:s3_content) { instance_double(Aws::S3::Types::Object, key: '123') }
|
80
|
+
|
81
|
+
it 'removes all letters', aggregates_failure: true do
|
82
|
+
expect(s3_client)
|
83
|
+
.to receive(:list_objects_v2)
|
84
|
+
.with(bucket: aws_bucket, prefix: '')
|
85
|
+
.and_return(
|
86
|
+
instance_double(Aws::S3::Types::ListObjectsV2Output, contents: [s3_content])
|
87
|
+
)
|
88
|
+
expect(s3_client)
|
89
|
+
.to receive(:delete_objects)
|
90
|
+
.with(
|
91
|
+
bucket: aws_bucket,
|
92
|
+
delete: {
|
93
|
+
objects: [key: s3_content.key],
|
94
|
+
quiet: false
|
95
|
+
}
|
96
|
+
)
|
97
|
+
|
98
|
+
described_class.destroy_all
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '.delete' do
|
103
|
+
let(:s3_content) { instance_double(Aws::S3::Types::Object, key: '123') }
|
104
|
+
|
105
|
+
it 'does not remove letter if it does not exist', aggregates_failure: true do
|
106
|
+
expect(s3_client)
|
107
|
+
.to receive(:list_objects_v2)
|
108
|
+
.with(bucket: aws_bucket, prefix: letter_id)
|
109
|
+
.and_return(
|
110
|
+
instance_double(Aws::S3::Types::ListObjectsV2Output, contents: [])
|
111
|
+
)
|
112
|
+
expect(s3_client)
|
113
|
+
.not_to receive(:delete_objects)
|
114
|
+
|
115
|
+
subject.delete
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'removes letter if it exists' do
|
119
|
+
expect(s3_client)
|
120
|
+
.to receive(:list_objects_v2)
|
121
|
+
.with(bucket: aws_bucket, prefix: letter_id)
|
122
|
+
.and_return(
|
123
|
+
instance_double(Aws::S3::Types::ListObjectsV2Output, contents: [s3_content])
|
124
|
+
)
|
125
|
+
.twice
|
126
|
+
|
127
|
+
expect(s3_client)
|
128
|
+
.to receive(:delete_objects)
|
129
|
+
.with(
|
130
|
+
bucket: aws_bucket,
|
131
|
+
delete: {
|
132
|
+
objects: [key: s3_content.key],
|
133
|
+
quiet: false
|
134
|
+
}
|
135
|
+
)
|
136
|
+
|
137
|
+
subject.delete
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '.valid?' do
|
142
|
+
it 'returns true if the letter exists' do
|
143
|
+
expect(s3_client)
|
144
|
+
.to receive(:list_objects_v2)
|
145
|
+
.with(bucket: aws_bucket, prefix: letter_id)
|
146
|
+
.and_return(
|
147
|
+
instance_double(Aws::S3::Types::ListObjectsV2Output, contents: [
|
148
|
+
instance_double(Aws::S3::Types::Object)
|
149
|
+
])
|
150
|
+
)
|
151
|
+
|
152
|
+
expect(subject).to be_valid
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'returns false if the letter does not exist' do
|
156
|
+
expect(s3_client)
|
157
|
+
.to receive(:list_objects_v2)
|
158
|
+
.with(bucket: aws_bucket, prefix: letter_id)
|
159
|
+
.and_return(
|
160
|
+
instance_double(Aws::S3::Types::ListObjectsV2Output, contents: [])
|
161
|
+
)
|
162
|
+
|
163
|
+
expect(subject).not_to be_valid
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '.attachments' do
|
168
|
+
let(:s3_content) { instance_double(Aws::S3::Types::Object, key: '123') }
|
169
|
+
let(:presigned_url) { 'https://presigned-url' }
|
170
|
+
let(:s3_bucket) { instance_double(Aws::S3::Bucket, object: double) }
|
171
|
+
let(:s3_object) { instance_double(Aws::S3::Object, presigned_url: double) }
|
172
|
+
|
173
|
+
it 'returns attachments with presigned urls' do
|
174
|
+
expect(s3_client)
|
175
|
+
.to receive(:list_objects_v2)
|
176
|
+
.with(bucket: aws_bucket, prefix: "#{letter_id}/attachments/")
|
177
|
+
.and_return(
|
178
|
+
instance_double(Aws::S3::Types::ListObjectsV2Output, contents: [s3_content])
|
179
|
+
)
|
180
|
+
expect(Aws::S3::Bucket)
|
181
|
+
.to receive(:new)
|
182
|
+
.with(name: aws_bucket, client: s3_client)
|
183
|
+
.and_return(s3_bucket)
|
184
|
+
expect(s3_bucket)
|
185
|
+
.to receive(:object)
|
186
|
+
.with(s3_content.key)
|
187
|
+
.and_return(s3_object)
|
188
|
+
expect(s3_object)
|
189
|
+
.to receive(:presigned_url)
|
190
|
+
.with(:get, expires_in: 1.week.to_i)
|
191
|
+
.and_return(presigned_url)
|
192
|
+
|
193
|
+
expect(subject.attachments).to eq(s3_content.key => presigned_url)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe LetterOpenerWeb::Letter do
|
4
|
+
let(:location) { Pathname.new(__dir__).join('..', '..', 'tmp').cleanpath }
|
5
|
+
|
6
|
+
def rich_text(mail_id)
|
7
|
+
<<~MAIL
|
8
|
+
Rich text for #{mail_id}
|
9
|
+
<!DOCTYPE html>
|
10
|
+
<body>
|
11
|
+
<div id="container">
|
12
|
+
<div id="message_headers">
|
13
|
+
<dl>
|
14
|
+
<dt>From:</dt>
|
15
|
+
<dd>noreply@example.com</dd>
|
16
|
+
</dl>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<a href='a-link.html'>
|
20
|
+
<img src='an-image.jpg'>
|
21
|
+
Link text
|
22
|
+
</a>
|
23
|
+
<a href='fooo.html'>Bar</a>
|
24
|
+
<a href="example.html" class="blank"></a>
|
25
|
+
<address><a href="inside-address.html">inside address</a></address>
|
26
|
+
</div>
|
27
|
+
</body>
|
28
|
+
MAIL
|
29
|
+
end
|
30
|
+
|
31
|
+
before :each do
|
32
|
+
LetterOpenerWeb.configure { |config| config.letters_location = location }
|
33
|
+
|
34
|
+
%w[1111_1111 2222_2222].each do |folder|
|
35
|
+
FileUtils.mkdir_p("#{location}/#{folder}")
|
36
|
+
File.write("#{location}/#{folder}/plain.html", "Plain text for #{folder}")
|
37
|
+
File.write("#{location}/#{folder}/rich.html", rich_text(folder))
|
38
|
+
FileUtils.mkdir_p("#{Rails.root.join('tmp', 'letter_opener')}/#{folder}")
|
39
|
+
File.write("#{Rails.root.join('tmp', 'letter_opener')}/#{folder}/rich.html", "Rich text for #{folder}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
after :each do
|
44
|
+
LetterOpenerWeb.reset!
|
45
|
+
FileUtils.rm_rf(location)
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'rich text headers' do
|
49
|
+
let(:id) { '1111_1111' }
|
50
|
+
subject { described_class.new(id:).headers }
|
51
|
+
|
52
|
+
before do
|
53
|
+
FileUtils.rm_rf("#{location}/#{id}/plain.html")
|
54
|
+
end
|
55
|
+
|
56
|
+
it { is_expected.to match(%r{<dl>\s*<dt>From:</dt>\s*<dd>noreply@example\.com</dd>}m) }
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'plain text headers' do
|
60
|
+
let(:id) { '1111_1111' }
|
61
|
+
subject { described_class.new(id:).headers }
|
62
|
+
|
63
|
+
before do
|
64
|
+
FileUtils.rm_rf("#{location}/#{id}/rich.html")
|
65
|
+
end
|
66
|
+
|
67
|
+
it { is_expected.to eq('UNABLE TO PARSE HEADERS') }
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'rich text version' do
|
71
|
+
let(:id) { '1111_1111' }
|
72
|
+
subject { described_class.new(id:).rich_text }
|
73
|
+
|
74
|
+
it { is_expected.to match(/Rich text for 1111_1111/) }
|
75
|
+
|
76
|
+
it 'changes links to show up on a new window' do
|
77
|
+
link_html = [
|
78
|
+
"<a href='a-link.html' target='_blank'>",
|
79
|
+
" <img src='an-image.jpg'/>",
|
80
|
+
' Link text',
|
81
|
+
'</a>'
|
82
|
+
].join("\n ")
|
83
|
+
|
84
|
+
expect(subject).to include(link_html)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'always rewrites links with a closing tag rather than making them selfclosing' do
|
88
|
+
expect(subject).to include("<a class='blank' href='example.html' target='_blank'></a>")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'plain text version' do
|
93
|
+
let(:id) { '2222_2222' }
|
94
|
+
subject { described_class.new(id:).plain_text }
|
95
|
+
|
96
|
+
it { is_expected.to match(/Plain text for 2222_2222/) }
|
97
|
+
end
|
98
|
+
|
99
|
+
describe 'default style' do
|
100
|
+
let(:id) { '2222_2222' }
|
101
|
+
subject { described_class.new(id:) }
|
102
|
+
|
103
|
+
it 'returns rich if rich text version is present' do
|
104
|
+
expect(subject.default_style).to eq('rich')
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'returns plain if rich text version is not present' do
|
108
|
+
allow(File).to receive_messages(exist?: false)
|
109
|
+
expect(subject.default_style).to eq('plain')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'attachments' do
|
114
|
+
let(:file) { 'an-image.csv' }
|
115
|
+
let(:attachments_dir) { "#{location}/#{id}/attachments" }
|
116
|
+
let(:id) { '1111_1111' }
|
117
|
+
|
118
|
+
subject { described_class.new(id:) }
|
119
|
+
|
120
|
+
before do
|
121
|
+
FileUtils.mkdir_p(attachments_dir)
|
122
|
+
File.open("#{attachments_dir}/#{file}", 'w') { |f| f.puts 'csv,contents' }
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'builds a hash with file name as key and full path as value' do
|
126
|
+
expect(subject.attachments).to eq(file => "#{attachments_dir}/#{file}")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '.search' do
|
131
|
+
let(:search_results) { described_class.search }
|
132
|
+
let(:first_letter) { search_results.first }
|
133
|
+
let(:last_letter) { search_results.last }
|
134
|
+
|
135
|
+
before do
|
136
|
+
allow(File).to receive(:mtime).with("#{location}/1111_1111").and_return(Date.today - 1.day)
|
137
|
+
allow(File).to receive(:mtime).with("#{location}/2222_2222").and_return(Date.today)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'returns a list of ordered letters' do
|
141
|
+
expect(first_letter.sent_at).to be > last_letter.sent_at
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe '.find' do
|
146
|
+
let(:id) { 'an-id' }
|
147
|
+
let(:letter) { described_class.find(id) }
|
148
|
+
|
149
|
+
it 'returns a letter with id set' do
|
150
|
+
expect(letter.id).to eq(id)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe '.destroy_all' do
|
155
|
+
it 'removes all letters' do
|
156
|
+
described_class.destroy_all
|
157
|
+
expect(Dir["#{location}/**/*"]).to be_empty
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe '#delete' do
|
162
|
+
let(:id) { '1111_1111' }
|
163
|
+
|
164
|
+
subject { described_class.new(id:).delete }
|
165
|
+
|
166
|
+
it 'removes the letter with given id' do
|
167
|
+
subject
|
168
|
+
|
169
|
+
directories = Dir["#{location}/*"]
|
170
|
+
expect(directories.count).to eql(1)
|
171
|
+
expect(directories.first).not_to match(id)
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'when the id is outside of the letters base path' do
|
175
|
+
let(:id) { '../3333_3333' }
|
176
|
+
|
177
|
+
it 'does not remove the letter' do
|
178
|
+
expect(FileUtils).not_to receive(:rm_rf).with(location.join(id).cleanpath.to_s)
|
179
|
+
|
180
|
+
expect(subject).to be_nil
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|