Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: Mail #462

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions Guardfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'active_support/inflector'

# Defines the matching rules for Guard.
guard :minitest, spring: 'bin/rails test', all_on_start: false do # rubocop:disable Metrics/BlockLength
watch(%r{^test/(.*)/?(.*)_test\.rb$})
Expand All @@ -9,9 +11,21 @@ guard :minitest, spring: 'bin/rails test', all_on_start: false do # rubocop:disa
watch(%r{^app/models/(.*?)\.rb$}) do |matches|
"test/models/#{matches[1]}_test.rb"
end
watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches|
"test/models/#{matches[1].singularize}_test.rb"
end
watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches|
resource_tests(matches[1])
end
watch(%r{^app/mailers/(.*?)\.rb$}) do |matches|
"test/mailers/#{matches[1]}_test.rb"
end
watch(%r{^test/fixtures/(.*)/(.*?)\.(html|text)$}) do |matches|
"test/mailers/#{matches[1]}_test.rb"
end
watch(%r{^lib/tasks/(.*?)\.rake$}) do |matches|
"test/tasks/#{matches[1]}_test.rb"
end
watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches|
["test/controllers/#{matches[1]}_controller_test.rb"] +
integration_tests(matches[1])
Expand All @@ -29,14 +43,8 @@ guard :minitest, spring: 'bin/rails test', all_on_start: false do # rubocop:disa
['test/controllers/sessions_controller_test.rb',
'test/integration/users_login_test.rb']
end
watch('app/controllers/account_activations_controller.rb') do
'test/integration/users_signup_test.rb'
end
watch(%r{app/views/users/*}) do
resource_tests('users') +
['test/integration/microposts_interface_test.rb']
end
end

# Returns the integration tests corresponding to the given resource.
def integration_tests(resource = :all)
if resource == :all
Expand Down
2 changes: 1 addition & 1 deletion app/mailers/application_mailer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com'
default from: 'no-reply@rezoleo.fr'
layout 'mailer'
end
17 changes: 17 additions & 0 deletions app/mailers/user_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

class UserMailer < ApplicationMailer
default from: email_address_with_name('no-reply@rezoleo.fr', 'Lea5')

def internet_expiration_7_days
@user = params[:user]
mail(to: email_address_with_name(@user.email, "#{@user.firstname} #{@user.lastname}"),
subject: 'Your internet will expire in 7 days')
end

def internet_expiration_1_day
@user = params[:user]
mail(to: email_address_with_name(@user.email, "#{@user.firstname} #{@user.lastname}"),
subject: 'Your internet will expire tomorrow')
end
end
16 changes: 8 additions & 8 deletions app/views/layouts/mailer.html.erb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
/* Email styles need to be inline */
</style>
</head>
</style>
</head>

<body>
<%= yield %>
</body>
<body>
<%= yield %>
</body>
</html>
1 change: 1 addition & 0 deletions app/views/user_mailer/internet_expiration_1_day.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello From HTML
1 change: 1 addition & 0 deletions app/views/user_mailer/internet_expiration_1_day.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from TEXT
1 change: 1 addition & 0 deletions app/views/user_mailer/internet_expiration_7_days.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello From HTML
1 change: 1 addition & 0 deletions app/views/user_mailer/internet_expiration_7_days.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from TEXT
10 changes: 10 additions & 0 deletions docs/features/internet_expiration_mail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Internet expiration mail

To avoid last minute renewal of subscriptions, we send an email to users 7 days and 1 day before their Internet access expires.
The task is defined in [`internet_expiration_mail.rake`](../../lib/tasks/internet_expiration_mail.rake), and runs every day.

> **Warning**
> The task does not currently handle running more than once a day, to prevent sending multiple emails.
> This means it also cannot "catch up" if an email was not sent.

The timer is done with service/timer of systemd, the configuration can be found in [systemd folder](../../lib/support/systemd).
8 changes: 4 additions & 4 deletions docs/features/subscription.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ graph TD
B --> |No| D[User's subscription ends now + subscription duration]
```

**Warning**
If a user has a free access when a subscription is added, the starting date
of the subscription is based on the subscription expiration status, *not* the internet
expiration status.
> **Warning**
> If a user has a free access when a subscription is added, the starting date
> of the subscription is based on the subscription expiration status, *not* the internet
> expiration status.
38 changes: 38 additions & 0 deletions lib/support/systemd/lea5-internet-expiration-mail.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[Unit]
Description=Lea5 - Send email warning users about their soon-to-expire subscription
Documentation=https://github.com/rezoleo/lea5

[Service]
# Command runs once then exits, it is not a background service
Type=oneshot
User=lea5
Group=lea5
WorkingDirectory=/opt/lea5
# Change start command for a new service
ExecStart=/opt/lea5/bin/rails lea5:internet_expiration_mail

Environment=PATH=/home/lea5/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=RAILS_ENV=production
Environment=RAILS_LOG_TO_STDOUT=1
Environment=RAILS_MASTER_KEY=<insert master key here>

NoNewPrivileges=true
#PrivateNetwork=true
PrivateDevices=true
PrivateMounts=true
PrivateTmp=true
PrivateUsers=true
ProtectHome=tmpfs
BindReadOnlyPaths=/home/lea5/.rbenv
ProtectSystem=strict
ReadWritePaths=/opt/lea5/log
ReadWritePaths=/opt/lea5/tmp
ReadWritePaths=/opt/lea5/storage
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
CapabilityBoundingSet=
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectProc=invisible
9 changes: 9 additions & 0 deletions lib/support/systemd/lea5-internet-expiration-mail.timer
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# See https://leethax.org/2017/11/17/systemd-timers.html
# and https://wiki.archlinux.org/title/Systemd/Timers
[Timer]
# Run the service every 24 hours
OnActiveSec=24h
OnUnitActiveSec=24h

[Install]
WantedBy=timer.target
19 changes: 19 additions & 0 deletions lib/tasks/internet_expiration_mail.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

namespace :lea5 do
desc 'send mail to users whose internet will expire soon'
task internet_expiration_mail: [:environment] do
# TODO: Handle multiple execution the same day (prevent resending email)
User.all.each do |user|
break if user.subscription_expiration.nil?

if 7.days.from_now.to_date == user.subscription_expiration.to_date
UserMailer.with(user:).internet_expiration_7_days.deliver_now
end

if 1.day.from_now.to_date == user.subscription_expiration.to_date
UserMailer.with(user:).internet_expiration_1_day.deliver_now
end
end
end
end
14 changes: 14 additions & 0 deletions test/fixtures/user_mailer/internet_expiration_1_day.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
/* Email styles need to be inline */
</style>
</head>

<body>
Hello From HTML

</body>
</html>
1 change: 1 addition & 0 deletions test/fixtures/user_mailer/internet_expiration_1_day.text
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from TEXT
14 changes: 14 additions & 0 deletions test/fixtures/user_mailer/internet_expiration_7_days.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
/* Email styles need to be inline */
</style>
</head>

<body>
Hello From HTML

</body>
</html>
1 change: 1 addition & 0 deletions test/fixtures/user_mailer/internet_expiration_7_days.text
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from TEXT
8 changes: 8 additions & 0 deletions test/mailers/previews/user_mailer_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview
def hello
UserMailer.hello
end
end
41 changes: 41 additions & 0 deletions test/mailers/user_mailer_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

require 'test_helper'

class UserMailerTest < ActionMailer::TestCase
test 'internet expiration 7 days' do
# Create the email and store it for further assertions
user = users(:ironman)
email = UserMailer.with(user:).internet_expiration_7_days

# Send the email, then test that it got queued
assert_emails 1 do
email.deliver_now
end

# Test the body of the sent email contains what we expect it to
assert_equal ['no-reply@rezoleo.fr'], email.from
assert_equal [user.email], email.to
assert_equal 'Your internet will expire in 7 days', email.subject
assert_equal read_fixture('internet_expiration_7_days.text').join.strip, email.text_part.body.to_s.strip
assert_equal read_fixture('internet_expiration_7_days.html').join.strip, email.html_part.body.to_s.strip
end

test 'internet expiration 1 day' do
# Create the email and store it for further assertions
user = users(:ironman)
email = UserMailer.with(user:).internet_expiration_1_day

# Send the email, then test that it got queued
assert_emails 1 do
email.deliver_now
end

# Test the body of the sent email contains what we expect it to
assert_equal ['no-reply@rezoleo.fr'], email.from
assert_equal [user.email], email.to
assert_equal 'Your internet will expire tomorrow', email.subject
assert_equal read_fixture('internet_expiration_1_day.text').join.strip, email.text_part.body.to_s.strip
assert_equal read_fixture('internet_expiration_1_day.html').join.strip, email.html_part.body.to_s.strip
end
end
50 changes: 50 additions & 0 deletions test/tasks/internet_expiration_mail_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

require 'rake'
require 'test_helper'

class InternetExpirationMailTest < ActionDispatch::IntegrationTest
# https://blog.10pines.com/2019/01/14/testing-rake-tasks/
# https://thoughtbot.com/blog/test-rake-tasks-like-a-boss
def setup
Rake.application.rake_require 'tasks/internet_expiration_mail'
Rake::Task.define_task(:environment)
@user = users(:ironman)
end

def teardown
# Once invoked, a rake task must be re-enabled to be executed a second time
# https://medium.com/@shaneilske/invoke-a-rake-task-multiple-times-1bcb01dee9d9
Rake::Task['lea5:internet_expiration_mail'].reenable
end

test 'should send an email when subscription expires in 7 days' do
@user.subscriptions.destroy_all
@user.subscriptions.new(start_at: Time.current, end_at: 7.days.from_now)
@user.save
assert_emails 1 do
Rake::Task['lea5:internet_expiration_mail'].invoke
end
assert_equal 'Your internet will expire in 7 days', UserMailer.deliveries.first.subject
end

test 'should send an email when subscription expires tomorrow' do
@user.subscriptions.destroy_all
@user.subscriptions.new(start_at: Time.current, end_at: 1.day.from_now)
@user.save
assert_emails 1 do
Rake::Task['lea5:internet_expiration_mail'].invoke
end

assert_equal 'Your internet will expire tomorrow', UserMailer.deliveries.first.subject
end

test 'should not send an email when subscription expires between 7 days and 1 day' do
@user.subscriptions.destroy_all
@user.subscriptions.new(start_at: Time.current, end_at: 6.days.from_now)
@user.save
assert_emails 0 do
Rake::Task['lea5:internet_expiration_mail'].invoke
end
end
end
1 change: 1 addition & 0 deletions test/tasks/sync_accounts_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'json'
require 'rake'
require 'webmock'
require 'test_helper'

class SyncAccountsTest < ActiveSupport::TestCase
# https://blog.10pines.com/2019/01/14/testing-rake-tasks/
Expand Down
Loading