move user credentials to a primary route, update twitter agents and background job

This commit is contained in:
Andrew Cantino 2014-02-01 14:01:27 -08:00
parent bf7d3dc859
commit ff758cc679
28 changed files with 423 additions and 153 deletions

View file

@ -6,7 +6,6 @@
#= require jquery.json-editor
#= require latlon_and_geo
#= require ./worker-checker
#= require ./users
#= require_self
window.setupJsonEditor = ($editor = $(".live-json-editor")) ->

View file

@ -1,13 +0,0 @@
//alert("i get included");
function remove_fields(link){
$(link).prev().val("1");
$(link).parent(".fields").hide();
}
function add_fields(link, association, content) {
var new_id = new Date().getTime();
var regexp = new RegExp("new_" + association, "g")
$(link).parent().before(content.replace(regexp, new_id));
}

View file

@ -2,28 +2,41 @@ module TwitterConcern
extend ActiveSupport::Concern
included do
self.validate :validate_twitter_options
self.after_initialize :configure_twitter
validate :validate_twitter_options
after_initialize :configure_twitter
end
def validate_twitter_options
unless options['consumer_key'].present? &&
options['consumer_secret'].present? &&
options['oauth_token'].present? &&
options['oauth_token_secret'].present?
errors.add(:base, "consumer_key, consumer_secret, oauth_token and oauth_token_secret are required to authenticate with the Twitter API")
unless twitter_consumer_key.present? &&
twitter_consumer_secret.present? &&
twitter_oauth_token.present? &&
twitter_oauth_token_secret.present?
errors.add(:base, "Twitter consumer_key, consumer_secret, oauth_token, and oauth_token_secret are required to authenticate with the Twitter API. You can provide these as options to this Agent, or as Credentials with the same names, but starting with 'twitter_'.")
end
end
def twitter_consumer_key
options['consumer_key'].presence || credential('twitter_consumer_key')
end
def twitter_consumer_secret
options['consumer_secret'].presence || credential('twitter_consumer_secret')
end
def twitter_oauth_token
options['oauth_token'].presence || options['access_key'].presence || credential('twitter_oauth_token')
end
def twitter_oauth_token_secret
options['oauth_token_secret'].presence || options['access_secret'].presence || credential('twitter_oauth_token_secret')
end
def configure_twitter
Twitter.configure do |config|
config.consumer_key = options['consumer_key']
config.consumer_secret = options['consumer_secret']
config.oauth_token = options['oauth_token'] || options['access_key']
config.oauth_token_secret = options['oauth_token_secret'] || options['access_secret']
config.consumer_key = twitter_consumer_key
config.consumer_secret = twitter_consumer_secret
config.oauth_token = twitter_oauth_token
config.oauth_token_secret = twitter_oauth_token_secret
end
end
module ClassMethods
end
end

View file

@ -0,0 +1,61 @@
class UserCredentialsController < ApplicationController
def index
@user_credentials = current_user.user_credentials.page(params[:page])
respond_to do |format|
format.html
format.json { render json: @user_credentials }
end
end
def new
@user_credential = current_user.user_credentials.build
respond_to do |format|
format.html
format.json { render json: @user_credential }
end
end
def edit
@user_credential = current_user.user_credentials.find(params[:id])
end
def create
@user_credential = current_user.user_credentials.build(params[:user_credential])
respond_to do |format|
if @user_credential.save
format.html { redirect_to user_credentials_path, notice: 'Your credential was successfully created.' }
format.json { render json: @user_credential, status: :created, location: @user_credential }
else
format.html { render action: "new" }
format.json { render json: @user_credential.errors, status: :unprocessable_entity }
end
end
end
def update
@user_credential = current_user.user_credentials.find(params[:id])
respond_to do |format|
if @user_credential.update_attributes(params[:user_credential])
format.html { redirect_to user_credentials_path, notice: 'Your credential was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @user_credential.errors, status: :unprocessable_entity }
end
end
end
def destroy
@user_credential = current_user.user_credentials.find(params[:id])
@user_credential.destroy
respond_to do |format|
format.html { redirect_to user_credentials_path }
format.json { head :no_content }
end
end
end

View file

@ -14,18 +14,4 @@ module ApplicationHelper
link_to '<span class="label label-warning">No</span>'.html_safe, agent_path(agent, :tab => (agent.recent_error_logs? ? 'logs' : 'details'))
end
end
def link_to_remove_fields(name, f, options = {})
f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)", options)
end
def link_to_add_fields(name, f, options = {})
association = options[:association]
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
render(association.to_s.singularize + "_fields", :f => builder)
end
link_to_function(name, "add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")")
end
end

View file

@ -59,10 +59,6 @@ class Agent < ActiveRecord::Base
where(:type => type)
}
def credential(name)
user.user_credentials.where(:credential_name => name).first.try(:credential_value) || nil
end
def check
# Implement me in your subclass of Agent.
end
@ -106,6 +102,20 @@ class Agent < ActiveRecord::Base
end
end
def credential(name)
@credential_cache ||= {}
if @credential_cache.has_key?(name)
@credential_cache[name]
else
@credential_cache[name] = user.user_credentials.where(:credential_name => name).first.try(:credential_value)
end
end
def reload
@credential_cache = {}
super
end
def new_event_expiration_date
keep_events_for > 0 ? keep_events_for.days.from_now : nil
end

View file

@ -8,10 +8,11 @@ module Agents
description <<-MD
The TwitterPublishAgent publishes tweets from the events it receives.
You [must set up a Twitter app](https://github.com/cantino/huginn/wiki/Getting-a-twitter-oauth-token) and provide it's `consumer_key`, `consumer_secret`, `oauth_token` and `oauth_token_secret`,
(also knows as "Access token" on the Twitter developer's site), along with the `username` of the Twitter user to publish as.
Twitter credentials must be supplied as either [credentials](/user_credentials) called
`twitter_consumer_key`, `twitter_consumer_secret`, `twitter_oauth_token`, and `twitter_oauth_token_secret`,
or as options to this Agent called `consumer_key`, `consumer_secret`, `oauth_token`, and `oauth_token_secret`.
The `oauth_token` and `oauth_token_secret` determine which user the tweet will be sent as.
To get oAuth credentials for Twitter, [follow these instructions](https://github.com/cantino/huginn/wiki/Getting-a-twitter-oauth-token).
You must also specify a `message_path` parameter: a [JSONPaths](http://goessner.net/articles/JsonPath/) to the value to tweet.
@ -19,10 +20,7 @@ module Agents
MD
def validate_options
unless options['username'].present? &&
options['expected_update_period_in_days'].present?
errors.add(:base, "username and expected_update_period_in_days are required")
end
errors.add(:base, "expected_update_period_in_days is required") unless options['expected_update_period_in_days'].present?
end
def working?
@ -31,12 +29,7 @@ module Agents
def default_options
{
'username' => "",
'expected_update_period_in_days' => "10",
'consumer_key' => "---",
'consumer_secret' => "---",
'oauth_token' => "---",
'oauth_token_secret' => "---",
'message_path' => "text"
}
end
@ -68,9 +61,8 @@ module Agents
end
end
def publish_tweet text
def publish_tweet(text)
Twitter.update(text)
end
end
end

View file

@ -6,11 +6,13 @@ module Agents
description <<-MD
The TwitterStreamAgent follows the Twitter stream in real time, watching for certain keywords, or filters, that you provide.
You must provide an oAuth `consumer_key`, `consumer_secret`, `oauth_token`, and `oauth_token_secret`, as well as an array of `filters`. Multiple words in a filter
must all show up in a tweet, but are independent of order.
To follow the Twitter stream, provide an array of `filters`. Multiple words in a filter must all show up in a tweet, but are independent of order.
If you provide an array instead of a filter, the first entry will be considered primary and any additional values will be treated as aliases.
Twitter credentials must be supplied as either [credentials](/user_credentials) called
`twitter_consumer_key`, `twitter_consumer_secret`, `twitter_oauth_token`, and `twitter_oauth_token_secret`,
or as options to this Agent called `consumer_key`, `consumer_secret`, `oauth_token`, and `oauth_token_secret`.
To get oAuth credentials for Twitter, [follow these instructions](https://github.com/cantino/huginn/wiki/Getting-a-twitter-oauth-token).
Set `expected_update_period_in_days` to the maximum amount of time that you'd expect to pass between Events being created by this Agent.
@ -67,13 +69,9 @@ module Agents
def default_options
{
'consumer_key' => "---",
'consumer_secret' => "---",
'oauth_token' => "---",
'oauth_token_secret' => "---",
'filters' => %w[keyword1 keyword2],
'expected_update_period_in_days' => "2",
'generate' => "events"
'filters' => %w[keyword1 keyword2],
'expected_update_period_in_days' => "2",
'generate' => "events"
}
end

View file

@ -9,7 +9,13 @@ module Agents
description <<-MD
The TwitterUserAgent follows the timeline of a specified Twitter user.
You [must set up a Twitter app](https://github.com/cantino/huginn/wiki/Getting-a-twitter-oauth-token) and provide it's `consumer_key`, `consumer_secret`, `oauth_token` and `oauth_token_secret`, (Also shown as "Access token" on the Twitter developer's site.) along with the `username` of the Twitter user to monitor.
Twitter credentials must be supplied as either [credentials](/user_credentials) called
`twitter_consumer_key`, `twitter_consumer_secret`, `twitter_oauth_token`, and `twitter_oauth_token_secret`,
or as options to this Agent called `consumer_key`, `consumer_secret`, `oauth_token`, and `oauth_token_secret`.
To get oAuth credentials for Twitter, [follow these instructions](https://github.com/cantino/huginn/wiki/Getting-a-twitter-oauth-token).
You must also provide the `username` of the Twitter user to monitor.
Set `expected_update_period_in_days` to the maximum amount of time that you'd expect to pass between Events being created by this Agent.
MD
@ -53,12 +59,8 @@ module Agents
def default_options
{
'username' => "tectonic",
'expected_update_period_in_days' => "2",
'consumer_key' => "---",
'consumer_secret' => "---",
'oauth_token' => "---",
'oauth_token_secret' => "---"
'username' => "tectonic",
'expected_update_period_in_days' => "2"
}
end

View file

@ -1,5 +1,6 @@
# Contacts are used only for the contact form on the Huginn website. If you host a public Huginn instance, you can use
# these to receive messages from visitors.
class Contact < ActiveRecord::Base
attr_accessible :email, :message, :name

View file

@ -22,10 +22,7 @@ class User < ActiveRecord::Base
validates_format_of :username, :with => /\A[a-zA-Z0-9_-]{3,15}\Z/, :message => "can only contain letters, numbers, underscores, and dashes, and must be between 3 and 15 characters in length."
validates_inclusion_of :invitation_code, :on => :create, :in => INVITATION_CODES, :message => "is not valid"
has_many :user_credentials, :dependent => :destroy
accepts_nested_attributes_for :user_credentials,
:allow_destroy => true
attr_accessible :user_credentials_attributes
has_many :user_credentials, :dependent => :destroy, :inverse_of => :user
has_many :events, :order => "events.created_at desc", :dependent => :delete_all, :inverse_of => :user
has_many :agents, :order => "agents.created_at desc", :dependent => :destroy, :inverse_of => :user
has_many :logs, :through => :agents, :class_name => "AgentLog"

View file

@ -1,6 +1,19 @@
class UserCredential < ActiveRecord::Base
attr_accessible :credential_name, :credential_value, :user_id
attr_accessible :credential_name, :credential_value
belongs_to :user
validates_presence_of :credential_name
validates_presence_of :credential_value
validates_presence_of :user_id
validates_uniqueness_of :credential_name, :scope => :user_id
before_save :trim_fields
protected
def trim_fields
credential_name.strip!
credential_value.strip!
end
end

View file

@ -1,9 +0,0 @@
<p class="fields">
<%= f.label :credential_name, "Name" %>
<%= f.text_field :credential_name %>
<%= f.label :credential_value, "Value" %>
<%= f.text_field :credential_value %>
<%= f.hidden_field :_destroy %>
<%= link_to_remove_fields("remove", f) %>
</p>

View file

@ -48,12 +48,6 @@
<div class='form-actions'>
<%= f.submit "Update", :class => "btn btn-primary" %>
</div>
<div class="control-group">
<%= f.fields_for(:user_credentials) do |uc| %>
<%= render 'user_credential_fields', :f => uc %>
<% end %>
</div>
<p><%= link_to_add_fields "Add A Credential", f, :association => :user_credentials %></p>
<% end %>
<h3>Cancel my account</h3>

View file

@ -4,6 +4,7 @@
<ul class='nav pull-left'>
<%= nav_link "Agents", agents_path %>
<%= nav_link "Events", events_path %>
<%= nav_link "Credentials", user_credentials_path %>
</ul>
<% end %>

View file

@ -0,0 +1,30 @@
<%= form_for(@user_credential, :method => @user_credential.new_record? ? "POST" : "PUT") do |f| %>
<% if @user_credential.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@user_credential.errors.count, "error") %> prohibited this Credential from being saved:</h2>
<ul>
<% @user_credential.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="control-group">
<%= f.label :credential_name, :class => 'control-label' %>
<div class="controls">
<%= f.text_field :credential_name, :class => 'span4' %>
</div>
</div>
<div class="control-group">
<%= f.label :credential_value, :class => 'control-label' %>
<div class="controls">
<%= f.text_area :credential_value, :class => 'span8', :rows => 10 %>
</div>
</div>
<div class='form-actions' style='clear: both'>
<%= f.submit "Save Credential", :class => "btn btn-primary" %>
</div>
<% end %>

View file

@ -0,0 +1,17 @@
<div class='container'>
<div class='row'>
<div class='span12'>
<div class="page-header">
<h2>
Editing your Credential
</h2>
</div>
<%= render 'form' %>
<div class="btn-group">
<%= link_to '<i class="icon-chevron-left"></i> Back'.html_safe, user_credentials_path, class: "btn" %>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,44 @@
<div class='container'>
<div class='row'>
<div class='span12'>
<div class="page-header">
<h2>
Your Credentials
</h2>
</div>
<blockquote>
Credentials are used to store values used by many Agents. Examples might include "twitter_consumer_secret",
"user_full_name", or "user_birthday".
</blockquote>
<table class='table table-striped'>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
<% @user_credentials.each do |user_credential| %>
<tr>
<td><%= user_credential.credential_name %></td>
<td>
<%= truncate user_credential.credential_value %>
<div class="btn-group" style="float: right">
<%= link_to 'Edit', edit_user_credential_path(user_credential), class: "btn btn-mini" %>
<%= link_to 'Delete', user_credential_path(user_credential), method: :delete, data: {confirm: 'Are you sure?'}, class: "btn btn-mini" %>
</div>
</td>
</tr>
<% end %>
</table>
<%= paginate @user_credentials, :theme => 'twitter-bootstrap' %>
<br/>
<div class="btn-group">
<%= link_to '<i class="icon-plus"></i> New Credential'.html_safe, new_user_credential_path, class: "btn" %>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,17 @@
<div class='container'>
<div class='row'>
<div class='span12'>
<div class="page-header">
<h2>
Create a new Credential
</h2>
</div>
<%= render 'form' %>
<div class="btn-group">
<%= link_to '<i class="icon-chevron-left"></i> Back'.html_safe, user_credentials_path, class: "btn" %>
</div>
</div>
</div>
</div>

View file

@ -18,16 +18,16 @@ require 'twitter/json_stream'
require 'em-http-request'
require 'pp'
def stream!(filters, options = {}, &block)
def stream!(filters, agent, &block)
stream = Twitter::JSONStream.connect(
:path => "/1/statuses/#{(filters && filters.length > 0) ? 'filter' : 'sample'}.json#{"?track=#{filters.map {|f| CGI::escape(f) }.join(",")}" if filters && filters.length > 0}",
:ssl => true,
:oauth => {
:consumer_key => options[:consumer_key],
:consumer_secret => options[:consumer_secret],
:access_key => options[:oauth_token] || options[:access_key],
:access_secret => options[:oauth_token_secret] || options[:access_secret]
},
:ssl => true
:consumer_key => agent.twitter_consumer_key,
:consumer_secret => agent.twitter_consumer_secret,
:access_key => agent.twitter_oauth_token,
:access_secret => agent.twitter_oauth_token_secret
}
)
stream.each_item do |status|
@ -55,7 +55,7 @@ def stream!(filters, options = {}, &block)
end
def load_and_run(agents)
agents.group_by { |agent| agent.options[:twitter_username] }.each do |twitter_username, agents|
agents.group_by { |agent| agent.twitter_oauth_token }.each do |oauth_token, agents|
filter_to_agent_map = agents.map { |agent| agent.options[:filters] }.flatten.uniq.compact.map(&:strip).inject({}) { |m, f| m[f] = []; m }
agents.each do |agent|
@ -64,11 +64,9 @@ def load_and_run(agents)
end
end
options = agents.first.options.slice(:consumer_key, :consumer_secret, :access_key, :oauth_token, :access_secret, :oauth_token_secret)
recent_tweets = []
stream!(filter_to_agent_map.keys, options) do |status|
stream!(filter_to_agent_map.keys, agents.first) do |status|
if status["retweeted_status"].present? && status["retweeted_status"].is_a?(Hash)
puts "Skipping retweet: #{status["text"]}"
elsif recent_tweets.include?(status["id_str"])

View file

@ -26,6 +26,8 @@ Huginn::Application.routes.draw do
end
end
resources :user_credentials, :except => :show
match "/worker_status" => "worker_status#show"
post "/users/:user_id/update_location/:secret" => "user_location_updates#create"

View file

@ -1,9 +1,9 @@
class CreateUserCredentials < ActiveRecord::Migration
def change
create_table :user_credentials do |t|
t.integer :user_id
t.string :credential_name
t.string :credential_value
t.integer :user_id, :null => false
t.string :credential_name, :null => false
t.text :credential_value, :null => false
t.timestamps
end

View file

@ -11,21 +11,21 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20140121075418) do
ActiveRecord::Schema.define(:version => 20140127164931) do
create_table "agent_logs", :force => true do |t|
t.integer "agent_id", :null => false
t.text "message", :null => false
t.integer "level", :default => 3, :null => false
t.integer "agent_id", :null => false
t.text "message", :limit => 16777215, :null => false
t.integer "level", :default => 3, :null => false
t.integer "inbound_event_id"
t.integer "outbound_event_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "agents", :force => true do |t|
t.integer "user_id"
t.text "options"
t.text "options", :limit => 16777215
t.string "type"
t.string "name"
t.string "schedule"
@ -37,27 +37,35 @@ ActiveRecord::Schema.define(:version => 20140121075418) do
t.datetime "updated_at", :null => false
t.text "memory", :limit => 2147483647
t.datetime "last_webhook_at"
t.integer "keep_events_for", :default => 0, :null => false
t.datetime "last_event_at"
t.datetime "last_error_log_at"
t.integer "keep_events_for", :default => 0, :null => false
end
add_index "agents", ["schedule"], :name => "index_agents_on_schedule"
add_index "agents", ["type"], :name => "index_agents_on_type"
add_index "agents", ["user_id", "created_at"], :name => "index_agents_on_user_id_and_created_at"
create_table "contacts", :force => true do |t|
t.text "message"
t.string "name"
t.string "email"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "delayed_jobs", :force => true do |t|
t.integer "priority", :default => 0
t.integer "attempts", :default => 0
t.text "handler"
t.text "last_error"
t.integer "priority", :default => 0
t.integer "attempts", :default => 0
t.text "handler", :limit => 16777215
t.text "last_error", :limit => 16777215
t.datetime "run_at"
t.datetime "locked_at"
t.datetime "failed_at"
t.string "locked_by"
t.string "queue"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority"
@ -65,11 +73,11 @@ ActiveRecord::Schema.define(:version => 20140121075418) do
create_table "events", :force => true do |t|
t.integer "user_id"
t.integer "agent_id"
t.decimal "lat", :precision => 15, :scale => 10
t.decimal "lng", :precision => 15, :scale => 10
t.text "payload", :limit => 16777215
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.decimal "lat", :precision => 15, :scale => 10
t.decimal "lng", :precision => 15, :scale => 10
t.text "payload", :limit => 2147483647
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.datetime "expires_at"
end
@ -88,9 +96,9 @@ ActiveRecord::Schema.define(:version => 20140121075418) do
add_index "links", ["source_id", "receiver_id"], :name => "index_links_on_source_id_and_receiver_id"
create_table "user_credentials", :force => true do |t|
t.integer "user_id"
t.string "credential_name"
t.string "credential_value"
t.integer "user_id", :null => false
t.string "credential_name", :null => false
t.text "credential_value", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end

View file

@ -0,0 +1,85 @@
require 'spec_helper'
describe UserCredentialsController do
def valid_attributes(options = {})
{
:credential_name => "some_name",
:credential_value => "some_value"
}.merge(options)
end
before do
sign_in users(:bob)
end
describe "GET index" do
it "only returns UserCredentials for the current user" do
get :index
assigns(:user_credentials).all? {|i| i.user.should == users(:bob) }.should be_true
end
end
describe "GET edit" do
it "only shows UserCredentials for the current user" do
get :edit, :id => user_credentials(:bob_aws_secret).to_param
assigns(:user_credential).should eq(user_credentials(:bob_aws_secret))
lambda {
get :edit, :id => user_credentials(:jane_aws_secret).to_param
}.should raise_error(ActiveRecord::RecordNotFound)
end
end
describe "POST create" do
it "creates UserCredentials for the current user" do
expect {
post :create, :user_credential => valid_attributes
}.to change { users(:bob).user_credentials.count }.by(1)
end
it "shows errors" do
expect {
post :create, :user_credential => valid_attributes(:credential_name => "")
}.not_to change { users(:bob).user_credentials.count }
assigns(:user_credential).should have(1).errors_on(:credential_name)
response.should render_template("new")
end
it "will not create UserCredentials for other users" do
expect {
post :create, :user_credential => valid_attributes(:user_id => users(:jane).id)
}.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
describe "PUT update" do
it "updates attributes on UserCredentials for the current user" do
post :update, :id => user_credentials(:bob_aws_key).to_param, :user_credential => { :credential_name => "new_name" }
response.should redirect_to(user_credentials_path)
user_credentials(:bob_aws_key).reload.credential_name.should == "new_name"
lambda {
post :update, :id => user_credentials(:jane_aws_key).to_param, :user_credential => { :credential_name => "new_name" }
}.should raise_error(ActiveRecord::RecordNotFound)
user_credentials(:jane_aws_key).reload.credential_name.should_not == "new_name"
end
it "shows errors" do
post :update, :id => user_credentials(:bob_aws_key).to_param, :user_credential => { :credential_name => "" }
assigns(:user_credential).should have(1).errors_on(:credential_name)
response.should render_template("edit")
end
end
describe "DELETE destroy" do
it "destroys only UserCredentials owned by the current user" do
expect {
delete :destroy, :id => user_credentials(:bob_aws_key).to_param
}.to change(UserCredential, :count).by(-1)
lambda {
delete :destroy, :id => user_credentials(:jane_aws_key).to_param
}.should raise_error(ActiveRecord::RecordNotFound)
end
end
end

View file

@ -1,8 +1,16 @@
bob_aws_key:
user: bob
credential_name: aws_key
credential_value: 2222222222
credential_value: 2222222222-bob
bob_aws_secret:
user: bob
credential_name: aws_secret
credential_value: 1111111111
credential_value: 1111111111-bob
jane_aws_key:
user: jane
credential_name: aws_key
credential_value: 2222222222-jane
jane_aws_secret:
user: jane
credential_name: aws_secret
credential_value: 1111111111-jabe

View file

@ -35,6 +35,15 @@ describe Agent do
it "should return nil when credential is not present" do
agents(:bob_weather_agent).credential("non_existing_credential").should == nil
end
it "should memoize the load" do
mock.any_instance_of(UserCredential).credential_value.twice { "foo" }
agents(:bob_weather_agent).credential("aws_secret").should == "foo"
agents(:bob_weather_agent).credential("aws_secret").should == "foo"
agents(:bob_weather_agent).reload
agents(:bob_weather_agent).credential("aws_secret").should == "foo"
agents(:bob_weather_agent).credential("aws_secret").should == "foo"
end
end
describe "changes to type" do

View file

@ -2,13 +2,28 @@ require 'spec_helper'
describe UserCredential do
describe "validation" do
it {should validate_uniqueness_of(:credential_name).scoped_to(:user_id)}
it { should validate_uniqueness_of(:credential_name).scoped_to(:user_id) }
it { should validate_presence_of(:credential_name) }
it { should validate_presence_of(:credential_value) }
it { should validate_presence_of(:user_id) }
end
describe "mass assignment" do
it {should allow_mass_assignment_of :credential_name}
it { should allow_mass_assignment_of :credential_name }
it {should allow_mass_assignment_of :credential_value}
it { should allow_mass_assignment_of :credential_value }
it {should allow_mass_assignment_of :user_id}
it { should_not allow_mass_assignment_of :user_id }
end
describe "cleaning fields" do
it "should trim whitespace" do
user_credential = user_credentials(:bob_aws_key)
user_credential.credential_name = " new name "
user_credential.credential_value = " new value "
user_credential.save!
user_credential.credential_name.should == "new name"
user_credential.credential_value.should == "new value"
end
end
end

View file

@ -4,24 +4,16 @@ describe User do
describe "validations" do
describe "invitation_code" do
it "only accepts valid invitation codes" do
User::INVITATION_CODES.each do |v|
should allow_value(v).for(:invitation_code)
end
User::INVITATION_CODES.each do |v|
should allow_value(v).for(:invitation_code)
end
end
it "can reject invalid invitation codes" do
%w['foo', 'bar'].each do |v|
should_not allow_value(v).for(:invitation_code)
end
%w['foo', 'bar'].each do |v|
should_not allow_value(v).for(:invitation_code)
end
end
end
end
describe "nested attributes" do
it { should accept_nested_attributes_for(:user_credentials).allow_destroy(true) }
end
describe "mass assignment" do
it {should allow_mass_assignment_of :user_credentials_attributes}
end
end