I have a website with several types of customer (eg Admin, Manager, Marketing, etc.)
Using Rails, I am asked to send test emails to those people so they can preview the emails on their own email client + firewall restrictions + see if the email go into the promotion folder or not. I need to be able to send a specific set of emails to each user type (such tests are very seldom, but ultimately any of our company admins should be able to send test emails using the frontend interface).
What I'm going for, it to write one class per user type, that would register the emails that this user is likely to receive at some point, and use (for example FactoryGirl) in build-only mode (no Writes to DB !!) to build the models needed to send the emails using deliver_now (so I avoid serialization/deserialization issues). I was hoping to be able to run this system in my real production environment (so that I can use my REAL email reputation, signatures, etc.)
Is there an easy way to disable DB writes (so I make sure all my example models are destroyed after their use to send an email ?) ? An easy option would be to boot up the server using readonly database credentials but maybe there is something safe that would avoid too much trouble.
Here is how my code looks like
module Testing
module Emails
class UserTypeAdmin < Base
attr_accessor, :new_user, :admin
register_email :created_new_user, type: :user_management do
UserManagementMailer.user_created(new_user, creator: admin)
end
def prepare_models
self.admin = FactoryGirl.build(:admin)
self.new_user = FactoryGirl.build(:user)
end
end
end
end
module Testing
module Emails
class Base
class < self
# my logic to register emails, definitions of #register_email, etc.
end
def initialize(tester_emails, ccs = [])
#tester_emails = tester_emails
#ccs = ccs
prepare_models
end
def send_email(email_name)
email = instance_eval(registered_emails(email_name))
email.to = #tester_emails
email.cc = #ccs
email.deliver_now
end
My FactoryGirls factories are quite messy and although I am using the :build methods, some factories were written using associations with the :create strategy so just to make sure, I'd like to lock the DB writes so I can easily prevent bad noise on my Database (I am using Mongoid so I don't have an easy transaction mechanism to cancel all my writes)
So one very simple solution is to write a spec that checks nothing is written to the DB. Using this I was able to debug a few cases where one model was persisted.
require 'rails_helper'
describe Testing::Email::UserTypeAdmin do
let(:tos) { ['admin#example.com'] }
let(:ccs) { ['adminmjg#example.com'] }
let(:tested_models) {[
User, Admin,
Conversation, Message, # etc.
]}
subject do
described_class.new(tos, ccs)
end
context 'testing all emails' do
it 'does nothing with the DB' do
subject.send_all_emails
aggregate_failures 'no persistence' do
tested_models.each do |model|
expect(model.count).to eq(0), "#{model.name} was persisted"
end
end
end
end
end
I'm still on the lookout for better solutions :-)
Related
I am using Rails 5.
I have an Affiliate model, with a boolean attribute email_notifications_on.
I am building a quite robust email drip system for affiliates and can't figure out where the best place is to check if the affiliate has email notifications on before delivering the email.
Most of my emails are being sent from Resque BG jobs, a few others from controllers.
Here is an example of how I am checking the subscribe status from a BG job:
class NewAffiliateLinkEmailer
#queue = :email_queue
def self.perform(aff_id)
affiliate = Affiliate.find(aff_id)
if affiliate.email_notifications_on?
AffiliateMailer.send_links(affiliate).deliver_now
end
end
end
It seems like writing if affiliate.email_notifications_on? in 10+ areas is not the right way to do this, especially if I need another condition to be met in the future. Or is this fine?
I thought maybe some sort of callback in the AffiliteMailer would work, but saw many people advising against business logic in the Mailer.
Any thoughts/advice would be appreciated.
To be honest, I don't think any better way than creating a method in Affiliate model as follows,
def should_send_email?
# all business logic come here
# to start with you will just have following
# email_notifications_on?
# later you can add `&&` or any business logic for more conditions
end
You can use this method instead of the attribute. It is more re-usable and extendable. You will still have to use the method in every call. If you like single liners then you can use lambda.
I created an RSpec spec to test if a POST #create action works properly:
describe "POST #create" do
it "creates a career" do
expect {
post "/careers/", career: attributes_for(:career)
}.to change(Career, :count).by 1
end
end
The above code works correctly. The issue happens when I create another test to allow only users with roles of "admin". Do I need to create a new user, log them in, and then run the above test? Do I need to do this for all future tests which have a restriction based on the user's role?
Is there another way to do this type of testing? 1) Just test if the create method works, and 2) only allow Users with "admin" role access the GET #new and POST #create methods?
When your feature is fully developed you'll want to have the following tests:
one happy-path test in which an admin creates a career
one in which a non-admin tries to create a career but is prevented from doing so
possibly another in which a not-logged-in user tries to create a career but is prevented from doing so (whether you want this depends on whether you have to write different code to handle non-logged-in and logged-in non-admin users), and
possibly other tests of different scenarios in which an admin creates a career.
This idea of having one complete, happy-path test is one of the most fundamental patterns in testing, but I'm not aware that it has a name, other than being implied by the term "happy path".
It looks like you're doing TDD. Great! To get from where you are now to the above list of tests, the next test to write is the one where the non-logged-in user is prevented from creating a career. To make both tests pass at the same time you'll need to change the first test to log in an admin. And if you need more tests of successfully creating a career (bullet 4), yes, you'll need to log in an admin in those too.
Side notes:
Unless you already have it, I'd write your happy-path spec not as a controller spec but as a feature spec (an acceptance test), so that you specify the important parts of the UI and integration-test the entire stack. Your failed-authentication specs might work as controller specs, although you might decide you need to acceptance-test the UI when a user doesn't have permissions for at least one of those scenarios.
I really don't like that expect {}.to change syntax. It prevents you from making any other expectations on the result of posting. In your example I would want to expect that the HTTP response status is 200 (response.should be_success). As I said, though, my first spec would be a feature spec, not a controller spec.
So, this is an interesting question. Yes, you should definitely (IMO) test authentication separately from the target method/action. Each of these constitutes a unit of functionality and should be tested as such.
In my current project, I'm favoring POROs (I often keep them in a directory called 'managers' although I know many people prefer to call them 'services') for all sorts of things because it lets me isolate functionality and test it independently. So, I might end up with something like:
# controllers/foo_controller.rb
class FooController < ApplicationController
before_action :authenticate
def create
#results = FooManager.create(params)
redirect_to (#results[:success] ? my_happy_path : my_sad_path)
end
def authenticate
redirect_to unauthorized_path unless AuthenticationManager.authenticate(params, request)
end
end
# managers/foo_manager.rb
class FooManager
class << self
def create(params)
# do a bunch of great stuff and return a hash (perhaps a
# HashWithIndifferentAccess, if you like) which will
# allow for evaluation of #results[:success] back in the
# controller.
end
end
end
# managers/authentication_manager.rb
class AuthenticationManager
class << self
def authenticate(params, request)
# do a bunch of great stuff and return a boolean
end
end
end
With an approach like this, I can very easily test FooManager.create and AuthenticationManager.authenticate (as well as FooController.create and FooController.authenticate routing) all independently. Hooray!
Now, whether your authentication framework or controller method is behaving correctly at a unit level, as Dave points out very well, is a separate issue from whether your entire system is behaving as expected. I'm with him on having high-level integration tests so you're clear about what 'done' looks like and you know when to holler 'ship it!'
Hi I'm using the Shopify gem in my Shopify app and I'm looking for suggestions on how to handle the API connection to Shopify.
I'm using webhooks and delayed_jobs so I need a way to open the connection outside of the controller.
At the moment I added this method to my Shop model:
def connect_to_store
session = ShopifyAPI::Session.new(self.url, self.access_token)
session.valid?
ShopifyAPI::Base.activate_session(session)
end
So I can open the connection very easily, for example:
Shop.find(1).connect_to_store
ShopifyAPI::Shop.current.name
The problem is that, inside my Product module, I need the connection open inside several methods but I end up calling the connect_to_store method several times and I'm worried about opening several connections to the same store, without a real need.
Is there a way to check if a connection is already opened and open a new one only if another one is not found?
Thanks,
Augusto
------------------- UPDATE -------------------
I explain better my issue.
Let's say that in my Product model I want to see if a given product has a compare_at_price greater than its price and, in this case, I want to add a "sale" tag to the Shopify product.
In my Product model I have:
class Product < ActiveRecord::Base
belongs_to :shop
def get_from_shopify
self.shop.connect_to_store
#shopify_p = ShopifyAPI::Product.find(self.shopify_id)
end
def add_tag(tag)
#shopify_p = self.get_from_shopify
shopify_p_tags = shopify_p.tags.split(",")
shopify_p_tags.collect{|x| x.strip!}
unless shopify_p_tags.include?(tag)
shopify_p_tags << tag
shopify_p_tags.join(",")
shopify_p.tags = shopify_p_tags
shopify_p.save
end
end
def on_sale?
#shopify_p = self.get_from_shopify
sale = false
shopify_p.variants.each do |v|
unless v.compare_at_price.nil?
if v.compare_at_price > v.price
sale = true
end
end
end
return sale
end
def update_sale_tag
if self.on_sale?
self.add_tag("sale")
end
end
end
My problem is that if I call:
p.update_sale_tag
the Shop.connect_to_store is called several times and I authenticate several times while I'm already authenticated.
How would you refactor this code?
I approach this by storing the OAuth token that is returned by Shopify with the store (you should be doing this anyway). All you need to access the API is the token, so in your shop model you would have a method like:
def shopify_api_path
"https://#{Rails.configuration.shopify_api_key}:#{self.shopify_token}##{self.shopify_domain}/admin"
end
Then if you want to access the API for a particular store in a Delayed Job worker, you would simply:
begin
ShopifyAPI::Base.site = shop.shopify_api_path
# Make whatever calls to the API that you want here.
products = ShopifyAPI::Product.all
ensure
ShopifyAPI::Base.site = nil
end
Hopefully that helps a little. I find working with Sessions outside of controllers to be a bit messy, particularly since this is nice and easy.
Once your application has authenticated once, you can hold on to that computed password – it’s good until the app is uninstalled for that particular store.
In other words, authenticate just the once when the merchant first installs the app, save the password to a db, and load it up whenever you need it. Your self.shop.connect_to_store call should then just set the ShopifyAPI::Session instance.
I think there is some misunderstanding here. You do know that you are really just using Active Resource for all your API work? And therefore when you authenticate, you are probably authenticating a session? And that once authenticated, no matter how many times you actually use the API, you're not actually opening "new" connections.
You are doing it wrong if you are constantly authenticating in a single session to do more than one API call.
If you happen to be in a block of code that has no authentication (for example your App may process a WebHook from N shops) or a Delayed Job, simply pass the myshopify_domain string to those code blocks, look up the Shop in your DB, find the auth token, authenticate (once)... and away you go... it really quite simple.
I have an existing site that has a bunch of different models and controllers. I am currently integrating Twilio's services into this site. Twilio allows you to supply a url that will be called when a user interacts with your phone number using their phone. Unfortunately, there is only one url that you can provide to Twilio and then all the parsing is done on your end.
So, now I have a twilio controller which parses the user's data and decides what they are trying to do.
Everything the user may be trying to do via their phone can be done on the website already, but now they have the option to use their phone when on the go. If they text my number "create group foo" then the site will try to create the group accordingly. My issue is that I already have a groups controller that knows how to create groups and has the appropriate before_filters to make sure that the user has permission to do so, amongst other things.
Is there a way for the twilio controller to parse the request and then "forward" it over to the proper controller in some way? I'd rather not have the twilio controller duplicate all of the code and filters that are in every other controller and some of that stuff doesn't feel right to be shoved into the models.
I'm somewhat new to rails in general, so I'm open to any suggestion. I'm hoping there's some design pattern out there that fits my use case and I'm willing to refactor my whole project for the correct solution.
I think there are a couple of things you can do. If you don't have to respond in a certain format, then you can simply redirect the request with the appropriately formatted parameters. For example:
class TwilioController
def create
if params[:twilio_action] == 'create group'
redirect_to create_group_path(:id => params[:group_id], :number => params[:number])
end
end
end
There's a good chance that you'll have problems with authentication though, because the twilio api will not be sending and receiving cookies for you, so you will not have an authenticated user. If this is the case it will be best to put all your shared code in the model and handle cookie authentication with your GroupsController and phone number authentication with your TwilioController. For example:
class TwilioController
def create
if params[:twilio_action] == 'create group'
if can_create_group?(params[:phone_number])
Group.create(:id => params[:group_id])
end
end
end
end
It's always best to put your business logic in your model, but if you do actually have a function you want to share within two controllers you can always create a module to do that as well:
module GroupControllerActions
def create_group user
Group.create(params[:group].merge({:user => user}))
end
end
class TwilioController
include GroupControllerActions
def create
if params[:twilio_action] == 'create group'
create_group(User.find_by_number(params[:phone_number]))
end
end
end
class GroupsController
def create
create_group(current_user)
end
end
I have a rails application that I am implementing the Twilio SMS API on, and I am a bit lost on how to test drive my design.
To start I've just made a model that is an SMS mailer that will encapsulate the twilio API and I want to be able to test it and ensure functionality without using up SMS credits or bombarding someone with test text messages.
I know how to implement the API and get it working in the code but what I need help with is actually testing the code to make sure it works and prevent breakage in the future. Could anyone provide some advice?
Thanks!
You could use my gem Twilio.rb, which is already tested, and then mock it out in your tests, e.g. with mocha
Twilio::SMS.expects(:create).with :to => '+19175551234', :from => '+12125551234', :body => 'this is easy!'
Your unit tests should never hit external services, they should always be mocked. This is follows from a general principle of unit testing that tests should not extend the class boundary of the object being tested and collaborator objects should be mocked/stubbed.
Hope this helps!
https://github.com/stevegraham/twilio-rb
My experience with testing, and with testing Twilio applications, is that you test to eliminate risk you add. You'll want to use the Twilio gem rather than rolling your own SMS code against their REST endpoint: this minimizes the amount of risk.
Wrap the API as thinly as possible in your business logic class, and test primarily the business logic. For example, in my system, SMSes get sent out of the Reminder class. The code looks something like this:
class SomeWrapperClass
if (RAILS_ENV == "testing")
##sent_smses = []
cattr_accessor :sent_smses
end
def send_a_message(to, from, message, callback_url = nil)
unless RAILS_ENV == "testing"
Twilio::SMS.message(to, from, message, callback_url)
else
##sent_smses << {:to => to, :from => from, :message => message, :callback_url => callback_url}
end
end
end
This lets me write tests focusing on my business logic, which is the stuff I'm going to screw up. For example, if I want to test some method send_reminder(client) which sends a SMS message:
test "sends reminder to client" do
SomeWrapperClass.sent_smses = []
client = clients(:send_reminder_test_case)
Reminder.send_reminder(client)
sent_message = SomeWrapperClass.sent_smses.last
assert !sent_message.blank?, "Sending a reminder should fire an SMS to client."
assert sent_message.index(client.name) >= 0, "Sending a reminder should fire an SMS with the client's name in it.
...
end
Now I'm testing the actual risk I've added, which is that I'm screwing up Reminder.send_reminder. The wrapper, on the other hand, should be close to risk-free.
Obviously separate as much of the logic as possible. By doing this you can test everything else around as much as possible and then only leave the calls to the external API needing tests.
Working with external API's can be tricky. One option is to mock the response to something that you know will work for you or to the response you would expect, this can obviously be a bit brittle though. Another option is to look at something like VCR. This will record the call to the external API once and play it back again whenever you call it again.
This guy seems to have started solving your problem: https://github.com/arfrank/Fake-Twilio-Api
You probably don't need to test twiliolib's code, but if you don't want to stub twiliolib's methods you could use the FakeWeb gem, where you define the response for specified requests.
Similar to Steve mentioned, I just stub out the request with mocha:
# In Twilio initializer
TWILIO_ACCOUNT = Twilio::RestAccount.new(TWILIO_CONFIG[:sid], TWILIO_CONFIG[:token])
# In a test helper file somewhere
class ActiveSupport::TestCase
# Call this whenever you need to test twilio requests
def stub_twilio_requests
# Stub the actual request to Twilio
TWILIO_ACCOUNT.stubs(:request).returns(Net::HTTPSuccess.new(nil, nil, nil).tap { |n|
n.stubs(:body).returns("<?xml version=\"1.0\"?>\n<TwilioResponse></TwilioResponse>\n")
})
end
end