I have a two-factor verification page, a secret key(Ciphertext) is displayed on it and I already have clipboard.js installed in my application.
I wonder how it is possible to create a button to copy that secret key?
= simple_form_for #google_auth, as: 'google_auth', url: verify_google_auth_path do |f|
h4 = t('.step-1')
p
span = t('.download-app')
span == t('.guide-link')
h4 = t('.step-2')
p: span = t('.scan-qr-code')
= f.input :uri do
= qr_tag(#google_auth.uri)
= f.input :otp_secret do
.input-group
= f.input_field :otp_secret, class: 'upcase', readonly: true
span.input-group-btn
a.btn.btn-default href='#{verify_google_auth_path(:app, refresh: true)}'
i.fa.fa-refresh
h4 = t('.step-3')
p: span = t('.enter-passcode')
= f.input :otp
hr.split
= f.button :wrapped, t('.submit'), cancel: settings_path
= content_for :guide do
ul.list-unstyled
li: a target='_blank' href='https://apps.apple.com/br/app/authy/id494168017'
i.fa.fa-apple
span = t('.ios')
li: a target='_blank' href='https://play.google.com/store/apps/details?id=com.authy.authy'
i.fa.fa-android
span = t('.android')
I tried to do like this, but it didn't work:
a.btn.btn-default data-clipboard-action='copy' data-clipboard-target=':otp_secret'
i.fa.fa-clipboard
In the example above it is copying only the pure otp_secret text.
spec\models\two_factor\app_spec.rb:
require 'spec_helper'
describe TwoFactor::App do
let(:member) { create :member }
let(:app) { member.app_two_factor }
describe "generate code" do
subject { app }
its(:otp_secret) { should_not be_blank }
end
describe '#refresh' do
context 'inactivated' do
it {
orig_otp_secret = app.otp_secret.dup
app.refresh!
expect(app.otp_secret).not_to eq(orig_otp_secret)
}
end
context 'activated' do
subject { create :two_factor_app, activated: true }
it {
orig_otp_secret = subject.otp_secret.dup
subject.refresh!
expect(subject.otp_secret).to eq(orig_otp_secret)
}
end
end
describe 'uniq validate' do
let(:member) { create :member }
it "reject duplicate creation" do
duplicate = TwoFactor.new app.attributes
expect(duplicate).not_to be_valid
end
end
describe 'self.fetch_by_type' do
it "return nil for wrong type" do
expect(TwoFactor.by_type(:foobar)).to be_nil
end
it "create new one by type" do
expect {
expect(app).not_to be_nil
}.to change(TwoFactor::App, :count).by(1)
end
it "retrieve exist one instead of creating" do
two_factor = member.app_two_factor
expect(member.app_two_factor).to eq(two_factor)
end
end
describe '#active!' do
subject { member.app_two_factor }
before { subject.active! }
its(:activated?) { should be_true }
end
describe '#deactive!' do
subject { create :two_factor_app, activated: true }
before { subject.deactive! }
its(:activated?) { should_not be_true }
end
describe '.activated' do
before { create :member, :app_two_factor_activated }
it "should has activated" do
expect(TwoFactor.activated?).to be_true
end
end
describe 'send_notification_mail' do
let(:mail) { ActionMailer::Base.deliveries.last }
describe "activated" do
before { app.active! }
it { expect(mail.subject).to match('Google authenticator activated') }
end
describe "deactived" do
let(:member) { create :member, :app_two_factor_activated }
before { app.deactive! }
it { expect(mail.subject).to match('Google authenticator deactivated') }
end
end
end
app.rb:
class TwoFactor::App < ::TwoFactor
def verify?
return false if otp_secret.blank?
rotp = ROTP::TOTP.new(otp_secret)
if rotp.verify(otp)
touch(:last_verify_at)
true
else
errors.add :otp, :invalid
false
end
end
def uri
totp = ROTP::TOTP.new(otp_secret)
totp.provisioning_uri(member.email) + "&issuer=#{ENV['URL_HOST']}"
end
def now
ROTP::TOTP.new(otp_secret).now
end
def refresh!
return if activated?
super
end
private
def gen_code
self.otp_secret = ROTP::Base32.random_base32
self.refreshed_at = Time.new
end
def send_notification
return if not self.activated_changed?
if self.activated
MemberMailer.google_auth_activated(member.id).deliver
else
MemberMailer.google_auth_deactivated(member.id).deliver
end
end
end
EDIT:
app\models\two_factor.rb:
class TwoFactor < ActiveRecord::Base
belongs_to :member
before_validation :gen_code, on: :create
after_update :send_notification
validates_presence_of :member, :otp_secret, :refreshed_at
attr_accessor :otp
SUBCLASS = ['app', 'sms', 'email', 'wechat']
validates_uniqueness_of :type, scope: :member_id
scope :activated, -> { where(activated: true) }
scope :require_signin, -> { where(require_signin: 1) }
class << self
def by_type(type)
return if not SUBCLASS.include?(type.to_s)
klass = "two_factor/#{type}".camelize.constantize
klass.find_or_create_by(type: klass.name)
end
def activated?
activated.any?
end
def require_signin?
require_signin.any?
end
end
def verify?
msg = "#{self.class.name}#verify? is not implemented."
raise NotImplementedError.new(msg)
end
def expired?
Time.now >= 30.minutes.since(refreshed_at)
end
def refresh!
gen_code
save
end
def active!
update activated: true, last_verify_at: Time.now
end
def set_require_signin
update require_signin: 1
end
def reset_require_signin
update require_signin: nil
end
def deactive!
update activated: false, require_signin: nil
end
private
def gen_code
msg = "#{self.class.name}#gen_code is not implemented."
raise NotImplementedError.new(msg)
end
def send_notification
msg = "#{self.class.name}#send_notification is not implemented."
raise NotImplementedError.new(msg)
end
end
What it seems you're trying to do is just to copy the value of an input field(which has been populated by other code you have) to the system clipboard. You need to use javascript to do this, if you have jquery this should work.
For your slim you need an id to target it
a.btn.btn-default id= "copy"
i.fa.fa-clipboard
Try to add an id to the input element you want to copy from
= f.input_field :otp_secret, class: 'upcase', id: "secret", readonly: true
Now try to change this and see if works.
a.btn.btn-default data-clipboard-action='copy' data-clipboard-target='secret'
i.fa.fa-clipboard
Also somewhere in your javascript you'll need to target the clip event with something like this:
new ClipboardJS('#secret');
See example here https://jsfiddle.net/ec3ywrzd/
Then you'll need this javascript to load in your html. But you'll need to be able to target the cipher field, in this example I'm using id="secret". I'm not sure if the OTP code you have generates it's own ID or now, so you may need to inspect your dom to figure out how to target it to add an ID. You may try adding an ID here:
= f.input_field :otp_secret, class: 'upcase', id: "secret", readonly: true
Otherwise you'll have to use other query selectors to target it.
But you may not need clipboardjs at all.
Here's a basic example on jsfiddle to test it you can just add any string to the input field. You'll need to add this to a JS file which will be loaded by your view layout, i.e. application.js
$(document).ready(function() {
$('#copy').click(function(){
$('#secret').select();
document.execCommand('copy');
alert("copied!");
})
})
You may also see answers to this question
I managed to solve based on suggestions from our friend #lacostenycoder.
There was only a need to change even in the show.html.slim file, looking like this:
= simple_form_for #google_auth, as: 'google_auth', url: verify_google_auth_path do |f|
h4 = t('.step-1')
p
span = t('.download-app')
span == t('.guide-link')
h4 = t('.step-2')
p: span = t('.scan-qr-code')
= f.input :uri do
= qr_tag(#google_auth.uri)
= f.input :otp_secret do
.input-group
.form-control.form-control-static = #google_auth.otp_secret
.input-group
a.btn.btn-default href="javascript:void(0)" data-clipboard-text = #google_auth.otp_secret
i.fa.fa-clipboard
a.btn.btn-default href='#{verify_google_auth_path(:app, refresh: true)}'
i.fa.fa-refresh
h4 = t('.step-3')
p: span = t('.enter-passcode')
= f.input :otp
hr.split
= f.button :wrapped, t('.submit'), cancel: settings_path
= content_for :guide do
ul.list-unstyled
li: a target='_blank' href='https://apps.apple.com/br/app/authy/id494168017'
i.fa.fa-apple
span = t('.ios')
li: a target='_blank' href='https://play.google.com/store/apps/details?id=com.authy.authy'
i.fa.fa-android
span = t('.android')
Related
I want to use this Ruby code to generate XML file with 10 terminals:
module WriteXML
def write_data_xml
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
xml.genesis {
xml.terminals {
/// create here some loop to iterate
xml.terminal {
xml.name "PPRO_Terminal"
xml.type "ppro"
xml.credentials {
xml.username 'user1'
xml.password 'passwd1'
xml.token '5e36e51de2dde626804a8772dc26238c4d722bbc'
}
}}
////////
}
end
puts builder.to_xml
file = File.new("credentials.xml", "w")
File.open('credentials.xml', 'w') do |file|
file << builder.to_xml
end
end
end
How I can use iteration in order to save code when I want to create many terminals?
Depends on where you keep the data that identify these terminals, is that in a table ? Then you could do something like this
def write_data_xml credential
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
xml.genesis {
xml.terminals {
xml.terminal {
xml.name credential.name
xml.type credential.type
xml.credentials {
xml.username credential.username
xml.password credential.password
xml.token credential.token
}
}}
}
end
File.open("credentials.xml", "a+") { |file| file.write builder.to_xml}
end
end
Suppose you use activerecord you could then
Credentials.each do |credential|
write_data_xml credential
end
If no table, you could use an array of structs where you gather the needed data.
EDIT on request of the OP, here a version that doesn't follow the single responsibility principle
def write_data_xml
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
xml.genesis {
xml.terminals {
Credentials.each do |credential|
xml.terminal {
xml.name credential.name
xml.type credential.type
xml.credentials {
xml.username credential.username
xml.password credential.password
xml.token credential.token
}
end
}}
}
end
File.write("credentials.xml", builder.to_xml)
end
end
EDIT2
here an example of how to use this with an array of structs since there is no database yet
Credentials = []
Credential = Struct.new(:name, :type, :username, :password, :token)
Credentials << Credential.new('PPRO_Terminal', 'ppro', 'user1', 'passwd1', '5e36e51de2dde626804a8772dc26238c4d722bbc')
Credentials << Credential.new( 'PPRO_Terminal2', 'ppro', 'user2', 'passwd2', '...')
p Credentials
[#<struct Credential name="PPRO_Terminal", type="ppro", username="user1", password="passwd1", token="5e36e51de2dde626804a8772dc26238c4d722bbc">, #<struct Credential name="PPRO_Terminal2", type="ppro", username="user2", password="passwd2", token="...">]
NB at least, create this outside of the method
i'm using ruby on rails 4.2.5 and filterrific and will_paginate 3.0.6 gems. But when i select any checkbox my javascript is broken and not work.
its my js file
$('.spin span:last-child').click(function(){
inputValue = parseInt($(this).parent().find($('.custom-input')).val());
inputValue += 1
$(this).parent().find($('.custom-input')).val(inputValue);
unitPrice = parseFloat($(this).closest('.product').find('.unit-price span').text().substring(3));
subTotal = parseFloat(inputValue * unitPrice).toFixed(2);
$(this).closest('.product').find('.total-price span').text(subTotal);
});
its my index.html.haml sort my products
.flex-container.products.wrapper.no-flex
.flex-container.relative
= form_for_filterrific #filterrific do |f|
.flex-item.sort-items<
Sort for
.select-style
= f.select(:sorted_by, #filterrific.select_options[:sorted_by], id: 'sort-select', include_blank: 'Seleccionar')
.filter-items.panel.panel-body
%h3.no-margin Mostrar por
%h4<
Type
%i.fa.fa-chevron-down>
%i.fa.fa-chevron-up
%ul
- #filterrific.select_options[:with_brand].each do |brand|
%li
= f.check_box :with_brand, { multiple: true, id: "filterrific_#{brand.name}", class: 'custom-checkbox' }, brand.id
= f.label brand.name, class: 'custom-label'
%h4<
Category
%i.fa.fa-chevron-down>
%i.fa.fa-chevron-up
my products_controller.rb
class ProductsController < ApplicationController
def index
#filterrific = initialize_filterrific(
DistributorProduct,
params[:filterrific],
select_options: {
sorted_by: DistributorProduct.options_for_sorted_by,
with_brand: Brand.select(:id, :name),
with_category: Category.select(:id, :name),
with_distributor: Distributor.select(:id, :name),
with_presentation: Presentation.select(:id, :name)
},
persistence_id: false
) || return
#distributor_products = #filterrific.find.page(params[:page]).search_query(params[:category], params[:search]).includes(:product)
#order_item = current_order.order_items.new
#brands = Brand.all
#categories = Category.all
#distributors = Distributor.all
#presentations = Presentation.all
end
end
and my distributor_product.rb model
class DistributorProduct < ActiveRecord::Base
filterrific(available_filters: [
:sorted_by,
:search_query,
:with_brand,
:with_category,
:with_distributor,
:with_presentation])
self.per_page = 9
belongs_to :distributor
belongs_to :product
has_many :sub_order_items
has_many :order_items
scope :search_query, lambda { |field, query|
return nil if query.blank?
query = "%#{query.downcase}%"
k, value = field.split('-')
key = k == 'sc' ? 'sub_categories' : 'categories'
joins(product: { categories: :sub_categories }).where(key => { id: value.to_s }).where('LOWER(products.name) LIKE ?', query).distinct
}
scope :sorted_by, lambda { |sort_option|
direction = (sort_option =~ /desc$/) ? 'desc' : 'asc'
case sort_option.to_s
when /^created_at_/
order("distributor_products.created_at #{direction}")
when /^name_/
order("distributor_products.name #{direction}")
when /^unit_price_/
order("distributor_products.unit_price #{direction}")
else
raise(ArgumentError, "Invalid sort option: #{sort_option.inspect}")
end
}
scope :with_brand, lambda { |brand_id|
return nil if brand_id.uniq == ['0']
joins(:product).where(products: { brand_id: brand_id })
}
scope :with_category, lambda { |category_id|
return nil if category_id.uniq == ['0']
joins(product: :categories).where(categories: { id: category_id })
}
scope :with_distributor, lambda { |distributor_id|
return nil if distributor_id.uniq == ['0']
where(distributor_id: distributor_id)
}
scope :with_presentation, lambda { |presentation_id|
return nil if presentation_id.uniq == ['0']
joins(:product).where(products: { presentation_id: presentation_id })
}
def self.options_for_sorted_by
[
['Nombre (a-z)', 'name_asc'],
['Nombre (z-a)', 'name_desc'],
['Precio (menor a mayor)', 'unit_price_asc'],
['Precio (mayor a menor)', 'unit_price_desc']
]
end
def image_url
"https://s3-sa-east-1.amazonaws.com/riqraops/catalogo/#{image}.png"
end
end
So , i have no idea why my js is not working when i have a checkbox selected , i need to reload the page for that :/ , thx for the responses.
The reason could be in the turbolinks. Try to add this to your Gemfile:
gem 'jquery-turbolinks'
Then run bundle install and add it to your application.js in this order:
//= require jquery
//= require jquery.turbolinks
//= require jquery_ujs
//
// ... your other scripts here ...
//
//= require turbolinks
Restart your server and try again. Hope this will help you.
I have such problem. My test checks whether the Observer called, but does not execute it.
My files:
todo_observer.rb:
class TodoObserver < ActiveRecord::Observer
def after_create(todo)
todo.add_log('creating')
end
end
todo.rb:
class Todo < ActiveRecord::Base
attr_accessible :content, :done, :order
validates :content, :presence => true,
:length => {:minimum => 2}
def add_log(event)
Logdata.start_logging(self.content, event)
end
end
logdata.rb
class Logdata < ActiveRecord::Base
attr_accessible :modification, :event
def self.start_logging(content, event)
Logdata.create!(:modification => content, :event => event)
end
end
todo_observer_spec.rb:
require 'spec_helper'
describe TodoObserver do
before(:each) do
#attr = { :modification => "Example", :event => 'Event' }
#attr_todo = { :content => "Example", :done => :false }
end
describe 'after_create' do
it "should create log about creating task" do
count_log = Logdata.all.size
todo = Todo.new(#attr_todo)
todo.should_receive(:add_log).with('creating')
todo.save!
(Logdata.all.size).should eq(count_log + 1)
end
end
end
When I run test I get such error
Failure/Error: (Logdata.all.size).should eq(count_log + 1)
expected: 1
got: 0
Its mean, that observer called,but doesn't create instance of Logdata. When I comment string(check the call)
todo.should_receive(:add_log).with('creating')
My tests were successful.And accordingly its success when I comment string (Logdata.all.size).should eq(count_log + 1)and uncomment previous string.
How does the function should_receive to create an instance of the class Logdata?
should_receive prevents the actual method from being called.
You should create two separate tests. One to check that the log is added to the todo, and one to check that the log is created.
describe 'after_create' do
it "should add a log to the todo" do
todo = Todo.new(#attr_todo)
todo.should_receive(:add_log).with('creating')
todo.save!
end
it "should create a new logdata" do
todo = Todo.new(#attr_todo)
expect {
todo.save!
}.to change {Logdata.count}.by(1)
end
end
Well I have a two models related with a on-to-many assoc.
#models/outline.rb
class Outline < ActiveRecord::Base
has_many :documents
end
#models/document.rb
class Document < ActiveRecord::Base
belongs_to :outline
end
#admin/outlines.rb
ActiveAdmin.register Outline do
form do |f|
f.inputs "Details" do
f.input :name, :required => true
f.input :pages, :required => true
...
f.buttons
end
f.inputs "Document Versions" do
f.has_many :documents, :name => "Document Versions" do |d|
d.input :file, :as => :file
d.buttons do
d.commit_button :title => "Add new Document Version"
end
end
end
end
end
Well as you can see in the admin/outlines.rb I already tried setting up the :name, in the has_many :documents, and the :title in the commit_button, but neither of that options work, I also tried with :legend, :title, and :label, instead of :name in the .has_many. Not working.
This is the result of that code:
Screenshot
What I want to display is "Document Versions" instead of "Documents", and "Add new Document Version" instead of "Add new Document"
If someone can have a solution it would be great
To set has_many header you can use
f.has_many :images, heading: 'My images' do |i|
i.input :src, label: false
end
See here
Looking at ActiveAdmin tests("should translate the association name in header"), there may be another way of doing this. Use your translation file.
If you look at ActiveAdmin has_many method (yuck!!! 46 lines of sequential code), it uses ActiveModel's human method.
Try adding this to your translation file
en:
activerecord:
models:
document:
one: Document Version
other: Document Versions
One quick hack is that you can hide the h3 tag through its style.
assets/stylesheets/active_admin.css.scss
.has_many {
h3 {
display: none;
}}
This will hide any h3 tag under a has_many class.
You can customise the label of the "Add..." button by using the new_record setting on has_many. For the heading label you can use heading:
f.has_many :documents,
heading: "Document Versions",
new_record: "Add new Document Version" do |d|
d.input :file, :as => :file
end
Sjors answer is actually a perfect start to solving the question. I monkeypatched Active Admin in config/initializers/active_admin.rb with the following:
module ActiveAdmin
class FormBuilder < ::Formtastic::FormBuilder
def titled_has_many(association, options = {}, &block)
options = { :for => association }.merge(options)
options[:class] ||= ""
options[:class] << "inputs has_many_fields"
# Set the Header
header = options[:header] || association.to_s
# Add Delete Links
form_block = proc do |has_many_form|
block.call(has_many_form) + if has_many_form.object.new_record?
template.content_tag :li do
template.link_to I18n.t('active_admin.has_many_delete'), "#", :onclick => "$(this).closest('.has_many_fields').remove(); return false;", :class => "button"
end
else
end
end
content = with_new_form_buffer do
template.content_tag :div, :class => "has_many #{association}" do
form_buffers.last << template.content_tag(:h3, header.titlecase) #using header
inputs options, &form_block
# Capture the ADD JS
js = with_new_form_buffer do
inputs_for_nested_attributes :for => [association, object.class.reflect_on_association(association).klass.new],
:class => "inputs has_many_fields",
:for_options => {
:child_index => "NEW_RECORD"
}, &form_block
end
js = template.escape_javascript(js)
js = template.link_to I18n.t('active_admin.has_many_new', :model => association.to_s.singularize.titlecase), "#", :onclick => "$(this).before('#{js}'.replace(/NEW_RECORD/g, new Date().getTime())); return false;", :class => "button"
form_buffers.last << js.html_safe
end
end
form_buffers.last << content.html_safe
end
end
end
Now in my admin file I call titled_has_many just like has_many but I pass in :header to override the use of the Association as the h3 tag.
f.titled_has_many :association, header: "Display this as the H3" do |app_f|
#stuff here
end
Does not deserve a prize but you could put this in config/initializers/active_admin.rb . It will allow you to tweak the headers you want using a config/locales/your_file.yml (you should create the custom_translations entry yourself). Dont forget to restart the server. And use the f.hacked_has_many in your form builder.
module ActiveAdmin
class FormBuilder < ::Formtastic::FormBuilder
def hacked_has_many(association, options = {}, &block)
options = { :for => association }.merge(options)
options[:class] ||= ""
options[:class] << "inputs has_many_fields"
# Add Delete Links
form_block = proc do |has_many_form|
block.call(has_many_form) + if has_many_form.object.new_record?
template.content_tag :li do
template.link_to I18n.t('active_admin.has_many_delete'), "#", :onclick => "$(this).closest('.has_many_fields').remove(); return false;", :class => "button"
end
else
end
end
content = with_new_form_buffer do
template.content_tag :div, :class => "has_many #{association}" do
# form_buffers.last << template.content_tag(:h3, association.to_s.titlecase)
# CHANGED INTO
form_buffers.last << template.content_tag(:h3, I18n.t('custom_translations.'+association.to_s))
inputs options, &form_block
# Capture the ADD JS
js = with_new_form_buffer do
inputs_for_nested_attributes :for => [association, object.class.reflect_on_association(association).klass.new],
:class => "inputs has_many_fields",
:for_options => {
:child_index => "NEW_RECORD"
}, &form_block
end
js = template.escape_javascript(js)
_model = 'activerecord.models.' + association.to_s.singularize
_translated_model = I18n.t(_model)
js = template.link_to I18n.t('active_admin.has_many_new', :model => _translated_model), "#", :onclick => "$(this).before('#{js}'.replace(/NEW_RECORD/g, new Date().getTime())); return false;", :class => "button"
form_buffers.last << js.html_safe
end
end
form_buffers.last << content.html_safe
end
end
end
If you have issues with locale files not being loaded good in staging/production mode, adding this to your application.rb might help (substitute :nl for the right locale)
config.before_configuration do
I18n.load_path += Dir[Rails.root.join('config','locales','*.{rb,yml}').to_s]
I18n.locale = :nl
I18n.default_locale = :nl
config.i18n.load_path += Dir[Rails.root.join('config','locales','*.{rb,yml}').to_s]
config.i18n.locale = :nl
config.i18n.default_locale = :nl
I18n.reload!
config.i18n.reload!
end
config.i18n.locale = :nl
config.i18n.default_locale = :nl
I am working on a library that needs to be able to download plugin files from a remote API using RestClient. The library first grabs a list of plugins, and then downloads each plugin as a raw file, saving each inside a plugins directory.
Here is what I have thus far but it is failing me:
require 'yaml'
module Monitaur
class Client
attr_accessor :logger, :client_key, :server_url, :config, :raw_config,
:plugin_manifest
def initialize
load_config
#plugin_manifest ||= []
end
def run
get_plugin_manifest
sync_plugins
end
def get_plugin_manifest
res = RestClient.get("#{server_url}/nodes/#{client_key}/plugins")
#plugin_manifest = JSON.parse(res)
end
def sync_plugins
#plugin_manifest.each do |plugin|
res = RestClient.get("#{server_url}/plugins/#{plugin['name']}")
File.open(File.join(Monitaur.plugin_dir, "#{plugin['name']}.rb"), "w+") do |file|
file.write res.body
end
end
end
def load_config
if File.exist?(Monitaur.config_file_path) && File.readable?(Monitaur.config_file_path)
#raw_config = YAML.load_file(Monitaur.config_file_path)
else
raise IOError, "Cannot open or read #{Monitaur.config_file_path}"
end
#server_url = raw_config['server_url']
#client_key = raw_config['client_key']
end
end
end
And the client_spec.rb
require 'spec_helper'
module Monitaur
describe Client do
let(:server_url) { "http://api.monitaurapp.com" }
let(:client_key) { "asdf1234" }
describe "#load_config" do
let(:client) { Monitaur::Client.new }
before do
File.open(Monitaur.config_file_path, "w") do |file|
file.puts "server_url: http://api.monitaurapp.com"
file.puts "client_key: asdf1234"
end
end
it "loads up the configuration file" do
client.load_config
client.server_url.should == "http://api.monitaurapp.com"
client.client_key.should == "asdf1234"
end
end
describe "#get_plugin_manifest" do
let(:client) { Monitaur::Client.new }
before do
stub_get_plugin_manifest
end
it "retrieves a plugins manifest from the server" do
client.get_plugin_manifest
client.plugin_manifest.should == plugin_manifest_response
end
end
describe "#sync_plugins" do
let(:client) { Monitaur::Client.new }
let(:foo_plugin) { mock('foo_plugin') }
let(:bar_plugin) { mock('bar_plugin') }
before do
FileUtils.mkdir("/tmp")
File.open("/tmp/foo_plugin.rb", "w+") do |file|
file.write %|
class FooPlugin < Monitaur::Plugin
name "foo_plugin"
desc "A test plugin to determine whether plugin sync works"
def run
{ :foo => 'foo' }
end
end
|
end
File.open("/tmp/bar_plugin.rb", "w+") do |file|
file.write %|
class BarPlugin < Monitaur::Plugin
name "bar_plugin"
desc "A test plugin to determine whether plugin sync works"
def run
{ :bar => 'bar' }
end
end
|
end
Monitaur.install
stub_get_plugin_manifest
stub_sync_plugins
client.get_plugin_manifest
end
it "downloads plugins to the cache directory" do
File.should_receive(:open).
with(File.join(Monitaur.plugin_dir, "foo_plugin.rb"), "w+")
and_yield(foo_plugin)
client.sync_plugins
File.exist?("/home/user/.monitaur/cache/plugins/foo_plugin.rb").should be_true
File.exist?("/home/user/.monitaur/cache/plugins/bar_plugin.rb").should be_true
end
end
end
end
def stub_get_plugin_manifest
stub_request(:get, "#{server_url}/nodes/#{client_key}/plugins").
to_return(
:status => 200,
:body => %Q{
[
{
"name": "foo_plugin",
"checksum": "qwer5678"
},
{
"name": "bar_plugin",
"checksum": "hjkl4321"
}
]
}
)
end
def plugin_manifest_response
[
{
"name" => "foo_plugin",
"checksum" => "qwer5678"
},
{
"name" => "bar_plugin",
"checksum" => "hjkl4321"
}
]
end
def stub_sync_plugins
stub_request(:get, "#{server_url}/plugins/foo_plugin").
to_return(:body => File.open('/tmp/foo_plugin.rb').read)
stub_request(:get, "#{server_url}/plugins/bar_plugin").
to_return(:body => File.open('/tmp/bar_plugin.rb').read)
end
How can I test the download process?
I use FakeWeb for this purpose, as there's really no need for your spec to fail if the other site is down or something. See "Replaying a recorded response" in the docs. What we do is curl the page, save it somewhere as a fixture and replay that in the specs.