another set of oauth work

This commit is contained in:
Mike Slemmer 2012-11-15 00:47:19 -08:00
commit b4f0d3b9eb
47 changed files with 933 additions and 77 deletions

View File

@ -28,6 +28,9 @@ gem 'tire'
gem 'rb-readline'
gem 'omniauth'
gem 'omniauth-facebook'
gem 'fb_graph'
gem 'sendgrid'
gem 'recaptcha'
group :development, :test do
gem 'rspec-rails', '2.11.0'

View File

@ -126,9 +126,7 @@ Message from Seth on sequence for creating/joining sessions:
self.location = '#/session/' + newSessionId;
});
}
).fail(function(jqXHR, textStatus, errorMessage) {
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
});
).fail(app.ajaxError);
return false;
}
@ -147,9 +145,7 @@ Message from Seth on sequence for creating/joining sessions:
data: invite
}).done(function(response) {
callCount--;
}).fail(function(jqXHR, textStatus, errorMessage) {
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
});
}).fail(app.ajaxError);
});
// TODO - this is the second time I've used this pattern.
// refactor to make a common utility for this.

View File

@ -9,6 +9,7 @@
context.JK.Header = function(app) {
var logger = context.JK.logger;
var searcher; // Will hold an instance to a JK.Searcher (search.js)
var userMe = null;
var instrumentAutoComplete;
var instrumentIds = [];
@ -97,9 +98,7 @@
data: JSON.stringify(user)
}).done(function(response) {
userMe = response;
}).fail(function(jqXHR, textStatus, errorMessage) {
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
});
}).fail(app.ajaxError);
return false;
}
@ -134,9 +133,7 @@
data: JSON.stringify(user)
}).done(function(response) {
userMe = response;
}).fail(function(jqXHR, textStatus, errorMessage) {
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
});
}).fail(app.ajaxError);
return false;
}
@ -149,9 +146,7 @@
}).done(function(r) {
userMe = r;
updateAccountForms();
}).fail(function(jqXHR, textStatus, errorMessage) {
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
});
}).fail(app.ajaxError);
}
function updateAccountForms() {
@ -178,6 +173,9 @@
events();
loadInstruments();
loadMe();
searcher = new JK.Searcher(app);
searcher.initialize();
};
};

View File

@ -76,6 +76,13 @@
context.JK.MessageType.LOGIN_ACK, loggedIn);
}
/**
* Generic error handler for Ajax calls.
*/
function ajaxError(jqXHR, textStatus, errorMessage) {
notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
}
/**
* Register for all known types, logging events as they happen, and
* notifying subscribers (see this.subscribe) as they occur.
@ -107,6 +114,11 @@
*/
this.fireEvent = handleMessage;
/**
* Expose ajaxError.
*/
this.ajaxError = ajaxError;
/**
* Provide a handler object for events related to a particular screen
* being shown or hidden.

View File

@ -27,7 +27,7 @@
client.JoinSession({ sessionID: session_id });
}
context.JK.refreshMusicSession(session_id);
});
}).fail(app.ajaxError);
};
})(window,jQuery);

View File

@ -0,0 +1,129 @@
(function(context,$) {
context.JK = context.JK || {};
context.JK.Searcher = function(app) {
var logger = context.JK.logger;
var searchSectionTemplate;
var searchItemTemplate;
var noResultsTemplate;
function events() {
$('.searchtextinput').keyup(handleKeyup);
$('.searchtextinput').focus(function(evt) {
var searchVal = $(this).val();
search(searchVal);
});
$('.searchtextinput').blur(hideSearchResults);
}
function templates() {
searchSectionTemplate = $('#template-search-section').html();
searchItemTemplate = $('#template-search-item').html();
noResultsTemplate = $('#template-search-noresults').html();
}
function hideSearchResults() {
$('.searchresults').hide();
}
function showSearchResults() {
$('.searchresults').show();
}
function handleKeyup(evt) {
if (evt.which === 27) {
return hideSearchResults();
}
var searchVal = $(this).val();
search(searchVal);
}
function search(query) {
if (query.length < 2) {
return;
}
$.ajax({
type: "GET",
url: "/api/search?query=" + query,
success: searchResponse,
error: app.ajaxError
});
}
function searchResponse(response) {
ensureResultsDiv();
updateResultsDiv(response);
positionResultsDiv();
showSearchResults();
}
function ensureResultsDiv() {
if ($('.searchresults').length === 0) {
$searchresults = $('<div/>');
$searchresults.addClass('searchresults');
$searchresults.css({position:'absolute'});
$('body').append($searchresults);
}
}
function updateResultsDiv(searchResults) {
var sections = ['musicians', 'bands', 'fans', 'recordings'];
var fullHtml = '';
$.each(sections, function() {
fullHtml += getSectionHtml(this, searchResults);
});
if (fullHtml === '') {
fullHtml += getNoResultsMessage();
}
$('.searchresults').html(fullHtml);
}
function getNoResultsMessage() {
// No replacement needed at the moment.
return noResultsTemplate;
}
function getSectionHtml(section, searchResults) {
if (section in searchResults && searchResults[section].length === 0) {
return '';
}
var items = '';
$.each(searchResults[section], function() {
items += getItemHtml(this);
});
var html = JK.fillTemplate(
searchSectionTemplate,
{ section: section, items: items });
return html;
}
function getItemHtml(item) {
var replacements = {
id: item.id,
name: item.first_name + " " + item.last_name,
image: item.photo_url,
subtext: item.location
};
return JK.fillTemplate(
searchItemTemplate, replacements);
}
function positionResultsDiv() {
var bodyOffset = $('body').offset();
var inputOffset = $('.searchtextinput').offset();
var inputHeight = $('.searchtextinput').outerHeight();
var resultsTop = bodyOffset.top + inputOffset.top + inputHeight;
var resultsLeft = bodyOffset.left + inputOffset.left;
$('.searchresults').css({
top: resultsTop + 'px',
left: resultsLeft + 'px'});
}
this.initialize = function() {
events();
templates();
};
};
})(window,jQuery);

View File

@ -0,0 +1,57 @@
/* Styles used by things related to search */
@import "client/common.css.scss";
/* Container for the search input */
.header .search {
position: absolute;
left: 50%;
margin-left: -125px;
top: 26px;
}
.searchtextinput {
border: 1px solid #fff;
background:none;
color:#fff;
font-size: 120%;
line-height: 120%;
width: 250px;
padding: 6px;
}
.searchresults {
background-color:$color8;
border:1px solid #000;
color:#000;
}
.searchresults h2 {
font-size: 120%;
font-weight: bold;
background-color: shade($color8, 10%);
}
.searchresults li {
clear:both;
padding: 8px;
}
.searchresults img {
float:left;
width: 32px;
height: 32px;
border: 1px solid #333;
margin: 4px;
}
.searchresults .text {
color: #000;
font-weight: bold;
}
.searchresults .subtext {
display:block;
color: #000;
font-size: 90%;
}

View File

@ -1,6 +1,6 @@
class ApiBandsController < ApplicationController
before_filter :signed_in_user, only: [:index, :show, :create, :update,
before_filter :api_signed_in_user, only: [:index, :show, :create, :update,
:following_create, :following_destroy]
respond_to :json

View File

@ -1,7 +1,7 @@
class ApiGenresController < ApiController
# have to be signed in currently to see this screen
before_filter :signed_in_user
before_filter :api_signed_in_user
respond_to :json

View File

@ -1,7 +1,7 @@
class ApiInstrumentsController < ApiController
# have to be signed in currently to see this screen
before_filter :signed_in_user
before_filter :api_signed_in_user
respond_to :json

View File

@ -1,7 +1,7 @@
class ApiInvitationsController < ApiController
# have to be signed in currently to see this screen
before_filter :signed_in_user
before_filter :api_signed_in_user
respond_to :json

View File

@ -1,7 +1,7 @@
class ApiMusicSessionsController < ApiController
# have to be signed in currently to see this screen
before_filter :signed_in_user
before_filter :api_signed_in_user
respond_to :json

View File

@ -1,7 +1,7 @@
class ApiSearchController < ApiController
# have to be signed in currently to see this screen
before_filter :signed_in_user
before_filter :api_signed_in_user
respond_to :json

View File

@ -1,6 +1,6 @@
class ApiUsersController < ApplicationController
class ApiUsersController < ApiController
before_filter :signed_in_user, only: [:index, :edit, :update, :delete,
before_filter :api_signed_in_user, only: [:index, :show, :edit, :update, :delete,
:friend_request_index, :friend_request_show,
:friend_request_create, :friend_request_update,
:friend_index, :friend_destroy]
@ -8,24 +8,48 @@ class ApiUsersController < ApplicationController
respond_to :json
def index
@users = User.paginate(page: params[:page])
# don't return users that aren't yet confirmed
@users = User.where('email_confirmed=TRUE').paginate(page: params[:page])
end
def show
@user = User.find(params[:id])
# don't return users that aren't yet confirmed
@user = User.where('email_confirmed=TRUE').find(params[:id])
end
# this API call is disabled by virtue of it being commented out in routes.rb
# the reason is that it has no captcha, and is therefore a bit abuseable
# if someone wants to use it, please add in captcha or some other bot-protector
def create
@user = User.save(params)
# check for errors
if @user.errors.nil? || @user.errors.size == 0
respond_with @user, responder: ApiResponder, :location => api_user_detail_url(@user)
# sends email to email account for confirmation
@user = UserManager.new.signup(params[:first_name],
params[:last_name],
params[:email],
params[:password],
params[:password_confirmation],
params[:city],
params[:state],
params[:country],
params[:instruments],
ApplicationHelper.base_uri(request) + "/confirm")
# check for errors
unless @user.errors.any?
render :json => {}, :status => :ok # an empty response, but 200 OK
else
raise ActiveRecord::Rollback
response.status = :unprocessable_entity
respond_with @user
respond_with @user, responder: ApiResponder
end
end
def signup_confirm
@user = UserManager.new.signup_confirm(params[:signup_token])
unless @user.errors.any?
respond_with @user, responder: ApiResponder, :location => api_user_detail_url(@user)
else
response.status = :unprocessable_entity
respond_with @user, responder: ApiResponder
end
end
@ -138,4 +162,21 @@ class ApiUsersController < ApplicationController
respond_with responder: ApiResponder
end
def auth_session_create
@user = User.authenticate(params[:email], params[:password])
if @user.nil?
render :json => { :success => false }, :status => 404
else
sign_in @user
render :json => { :success => true }, :status => 200
end
end
def auth_session_delete
sign_out
render :json => { :success => true }, :status => 200
end
end

View File

@ -8,12 +8,13 @@ class SessionsController < ApplicationController
# http://net.tutsplus.com/tutorials/ruby/how-to-use-omniauth-to-authenticate-your-users/
def create
user = User.find_by_email(params[:session][:email])
if user && user.authenticate(params[:session][:password])
complete_sign_in user
else
user = User.authenticate(params[:session][:email], params[:session][:password])
if user.nil?
flash.now[:error] = 'Invalid email/password combination'
render 'new'
else
complete_sign_in user
end
end
@ -30,9 +31,26 @@ class SessionsController < ApplicationController
#
# For debugging - to see what all is there:
# render :text => auth_hash.to_yaml
user = User.new :name => auth_hash["info"]["name"], :email => auth_hash["info"]["email"]
user.authorizations.build :provider => auth_hash["provider"], :uid => auth_hash["uid"],
:token => auth_hash["credentials"]["token"], :token_expiration => auth_hash["credentials"]["expires_at"]
#FbGraph.debug!
#app = FbGraph::Application.new '468555793186398', :secret => '546a5b253972f3e2e8b36d9a3dd5a06e'
token = auth_hash[:credentials][:token]
# FIXME:
# This should probably be in a transaction somehow, meaning the user
# create and the authorization create. Concern is UserManager.new.signup sends
# an email and whatnot.
user = UserManager.new.signup(auth_hash[:info][:first_name],
auth_hash[:info][:last_name],
auth_hash[:info][:email],
nil,
nil,
auth_hash[:info][:location],
nil, # state
nil, # @country
nil,
nil)
user.user_authorizations.build :provider => auth_hash[:provider], :uid => auth_hash[:uid],
:token => auth_hash[:credentials][:token], :token_expiration => auth_hash[:credentials][:expires_at]
user.save
complete_sign_in user
end

View File

@ -1,9 +1,10 @@
class UsersController < ApplicationController
before_filter :signed_in_user,
before_filter :signed_in_user,
only: [:index, :edit, :update, :destroy]
before_filter :correct_user, only: [:edit, :update]
before_filter :admin_user, only: :destroy
def index
@users = User.paginate(page: params[:page])
end
@ -17,12 +18,49 @@ class UsersController < ApplicationController
end
def create
@user = User.new(params[:jam_ruby_user])
if @user.save
sign_in @user, :new => true
else
@user = User.new
# check recaptcha; if any errors seen, contribute it to the model
unless verify_recaptcha(:model => @user, :message => "recaptcha")
render 'new'
return
end
@user = UserManager.new.signup(params[:jam_ruby_user][:name],
params[:jam_ruby_user][:email],
params[:jam_ruby_user][:password],
params[:jam_ruby_user][:password_confirmation],
params[:jam_ruby_user][:city],
params[:jam_ruby_user][:state],
params[:jam_ruby_user][:country],
params[:jam_ruby_user][:instruments],
ApplicationHelper.base_uri(request) + "/confirm")
# check for errors
if @user.errors.any?
# render any @user.errors on error
render 'new'
else
# if success, redirect to 'email_sent' page
flash[:success] = "Please check your email and confirm your signup"
redirect_to :email_sent
end
end
def email_sent
end
def signup_confirm
@user = UserManager.new.signup_confirm(params[:signup_token])
unless @user.nil? || @user.errors.any?
sign_in @user
redirect_to :client
end
# let errors fall through to signup_confirm.html.erb
end
def edit

View File

@ -9,4 +9,8 @@ module ApplicationHelper
"#{base_title} | #{page_title}"
end
end
def self.base_uri(request)
(request.ssl? ? "https://" : "http://") + request.host_with_port
end
end

View File

@ -1,10 +1,6 @@
module SessionsHelper
def sign_in(user)
if (:new was set to true)
flash[:success] = "Welcome to Jamkazam!"
redirect_to user
end
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
@ -32,6 +28,13 @@ module SessionsHelper
end
end
def api_signed_in_user
unless signed_in?
render :json => {}, :status => 403
end
end
def sign_out
current_user = nil
cookies.delete(:remember_token)

View File

@ -10,10 +10,6 @@ node :last_name do |follower|
follower.user.last_name
end
node :name do |follower|
follower.user.name
end
node :city do |follower|
follower.user.city
end

View File

@ -4,7 +4,7 @@ attributes :id, :name, :city, :state, :country, :website, :biography, :photo_url
unless @band.users.nil? || @band.users.size == 0
child :users => :musicians do
attributes :id, :first_name, :last_name, :name, :photo_url
attributes :id, :first_name, :last_name, :photo_url
# TODO: figure out how to omit empty arrays
node :instruments do |user|

View File

@ -5,7 +5,7 @@ child(:bands => :bands) {
}
child(:musicians => :musicians) {
attributes :id, :first_name, :last_name :location, :photo_url
attributes :id, :first_name, :last_name, :location, :photo_url
}
child(:fans => :fans) {
@ -14,4 +14,4 @@ child(:fans => :fans) {
child(:recordings => :recordings) {
attributes :id, :name
}
}

View File

@ -10,10 +10,6 @@ node :last_name do |follower|
follower.user.last_name
end
node :name do |follower|
follower.user.name
end
node :city do |follower|
follower.user.city
end

View File

@ -10,10 +10,6 @@ node :last_name do |following|
following.user.last_name
end
node :name do |follower|
follower.user.name
end
node :city do |following|
following.user.city
end

View File

@ -1,3 +1,3 @@
object @user.friends
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :email, :online
attributes :id, :first_name, :last_name, :city, :state, :country, :email, :online

View File

@ -1,4 +1,4 @@
collection @users
# do not retrieve all child collections when showing a list of users
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :email, :online, :musician, :photo_url
attributes :id, :first_name, :last_name, :city, :state, :country, :email, :online, :musician, :photo_url

View File

@ -1,10 +1,10 @@
object @user
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :email, :online, :photo_url, :friend_count, :follower_count, :following_count
attributes :id, :first_name, :last_name, :city, :state, :country, :online, :photo_url, :gender, :birth_date, :internet_service_provider, :friend_count, :follower_count, :following_count
unless @user.friends.nil? || @user.friends.size == 0
child :friends => :friends do
attributes :id, :first_name, :last_name, :name, :online
attributes :id, :first_name, :last_name, :online
end
end

View File

@ -0,0 +1,3 @@
object @user
extends "api_users/show"

View File

@ -1,6 +1,32 @@
<div class="curtain"></div>
<div layout="header" class="header">
<h1>JamKazam</h1>
<div class="search">
<input type="text" class="searchtextinput" placeholder="Search for Bands, Musicians and Fans"/>
</div>
<script type="text/template" id="template-search-section">
<h2>{section}</h2>
<ul>
{items}
</ul>
</script>
<script type="text/template" id="template-search-noresults">
<h2 class="emptyresult">No Matches</h2>
<p>No results returned</p>
</script>
<script type="text/template" id="template-search-item">
<li>
<a>
<img src="{image}"/>
<span class="text">{name}</span>
<span class="subtext">{subtext}</span>
</a>
</li>
</script>
<div class="userinfo">
<%= gravatar_for current_user, size: 52, hclass: "avatar medium" %>
<div class="username">

View File

@ -7,6 +7,7 @@
<![endif]-->
<%= stylesheet_link_tag "client/ie", media: "all" %>
<%= stylesheet_link_tag "client/jamkazam", media: "all" %>
<%= stylesheet_link_tag "client/search", media: "all" %>
<%= stylesheet_link_tag "client/lato", media: "all" %>
<%= include_gon %>
<%= javascript_include_tag "application" %>

View File

@ -0,0 +1,8 @@
<% provide(:title, 'Confirmation Email Sent') %>
<h1>Confirm Email Next</h1>
<div class="row">
<div class="span6 offset3">
Do it.
</div>
</div>

View File

@ -17,12 +17,23 @@
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :city %>
<%= f.text_field :city %>
<%= f.label :state %>
<%= f.text_field :state %>
<%= f.label :country %>
<%= f.text_field :country %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %>
<%= recaptcha_tags %>
<%= f.submit "Create my account", class: "btn btn-large btn-primary" %>
<% end %>
</div>

View File

@ -0,0 +1,8 @@
<% provide(:title, 'Signup Confirmation') %>
<h1>Signup Confirmation Failure</h1>
<div class="row">
<div class="span6 offset3">
Unable to confirm registration email
</div>
</div>

View File

@ -26,6 +26,7 @@ module SampleApp
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
config.autoload_paths += %W(#{config.root}/lib/managers)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.

View File

@ -0,0 +1,11 @@
ActionMailer::Base.raise_delivery_errors = true
ActionMailer::Base.delivery_method = Rails.env == "test" ? :test : :smtp
ActionMailer::Base.smtp_settings = {
:address => "smtp.sendgrid.net",
:port => 587,
:domain => "www.jamkazam.com",
:authentication => :plain,
:user_name => "jamkazam",
:password => "jamjamblueberryjam",
:enable_starttls_auto => true
}

View File

@ -1,3 +1,3 @@
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, '468555793186398', '546a5b253972f3e2e8b36d9a3dd5a06e'
provider :facebook, '468555793186398', '546a5b253972f3e2e8b36d9a3dd5a06e', {:scope => 'email,user_location'}
end

View File

@ -0,0 +1,17 @@
# this gem turns recaptcha verification off during tests by default.
# The public key/private keys shown below valid for all jamkazam.com domains
# note that all recaptcha keys work on localhost/127.0.0.1
# the keys are created at https://www.google.com/recaptcha/admin/create
Recaptcha.configure do |config|
# created using seth@jamkazam.com; can't see way to delegate
config.public_key = '6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B'
config.private_key = '6Let8dgSAAAAAJzFxL9w2QR5auxjk0ol1_xAtOGO'
# other config options available with this gem:
#nonssl_api_server_url,
#ssl_api_server_url,
#verify_url,
#skip_verify_env,
#proxy,
#handle_timeouts_gracefully
end

View File

@ -14,6 +14,7 @@ SampleApp::Application.routes.draw do
root to: 'static_pages#home'
match '/signup', to: 'users#new'
match '/email_sent', to: 'users#email_sent'
match '/signin', to: 'sessions#new'
match '/signout', to: 'sessions#destroy', via: :delete
# oauth
@ -26,6 +27,8 @@ SampleApp::Application.routes.draw do
match '/client', to: 'clients#index'
match '/confirm/:signup_token', to: 'users#signup_confirm'
scope '/api' do
# music sessions
match '/sessions/:id/participants' => 'api_music_sessions#participant_create', :via => :post
@ -42,9 +45,14 @@ SampleApp::Application.routes.draw do
# users
match '/users' => 'api_users#index', :via => :get
match '/users/:id' => 'api_users#show', :via => :get, :as => 'api_user_detail'
match '/users' => 'api_users#create', :via => :post
#match '/users' => 'api_users#create', :via => :post
match '/users/:id' => 'api_users#update', :via => :post
match '/users/:id' => 'api_users#destroy', :via => :delete
match '/users/confirm/:signup_token' => 'api_users#signup_confirm', :via => :post, :as => 'api_signup_confirmation'
# login/logout
match '/auth_sessions' => 'api_users#auth_session_create', :via => :post
match '/auth_sessions' => 'api_users#auth_session_delete', :via => :delete
# friend requests
match '/users/:id/friend_requests' => 'api_users#friend_request_index', :via => :get

View File

@ -0,0 +1,41 @@
require 'recaptcha'
class UserManager < BaseManager
include Recaptcha::Verify
def initialize(options={})
super(options)
@log = Logging.logger[self]
end
def signup(first_name, last_name, email, password, password_confirmation,
city, state, country, instruments, signup_confirm_url)
@user = User.new
# TODO: figure out why can't user verify_recaptcha here
# ALSO: make sure we dont do the recaptcha stuff if used facebook.
# check recaptcha; if any errors seen, contribute it to the model
#unless verify_recaptcha(:model => @user, :message => "recaptcha")
# return @user # @user.errors.any? is true now
#else
# sends email to email account for confirmation
@user = User.signup(first_name, last_name, email, password, password_confirmation,
city, state, country, instruments, signup_confirm_url)
return @user
#end
end
def signup_confirm(signup_token)
begin
@user = User.signup_confirm(signup_token)
rescue ActiveRecord::RecordNotFound
@user = nil
end
return @user
end
end

View File

@ -4,6 +4,7 @@ FactoryGirl.define do
sequence(:email) { |n| "person_#{n}@example.com"}
password "foobar"
password_confirmation "foobar"
email_confirmed true
factory :admin do
admin true
@ -26,4 +27,8 @@ FactoryGirl.define do
factory :invitation, :class => JamRuby::Invitation do
end
factory :band, :class => JamRuby::Band do
end
end

View File

@ -0,0 +1,24 @@
<!-- Fixtures for Jasmine Tests for searcher.js -->
<input type="text" class="searchtextinput" placeholder="Search for Magical Things" />
<script type="text/template" id="template-search-section">
<h2>{section}</h2>
<ul>
{items}
</ul>
</script>
<script type="text/template" id="template-search-noresults">
<h2 class="emptyresult">No Matches</h2>
<p>No results returned</p>
</script>
<script type="text/template" id="template-search-item">
<li>
<a>
<img src="{image}"/>
<span class="text">{name}</span>
<span class="subtext">{subtext}</span>
</a>
</li>
</script>

View File

@ -0,0 +1,62 @@
window.TestResponses = {
search : {
bands: [ ],
musicians: [ ],
fans: [
{
id: "1",
first_name: "Test",
last_name: "User",
location: "Austin, TX",
photo_url: "http://www.jamkazam.com/images/users/photos/1.gif"
}
],
recordings: [ ]
},
emptySearch: {
bands: [],
musicians: [],
fans: [],
recordings: []
},
fullSearch: {
bands: [
{
id: "1",
first_name: "Test",
last_name: "User",
location: "Austin, TX",
photo_url: "http://www.jamkazam.com/images/users/photos/1.gif"
}
],
musicians: [
{
id: "1",
first_name: "Test",
last_name: "User",
location: "Austin, TX",
photo_url: "http://www.jamkazam.com/images/users/photos/1.gif"
}
],
fans: [
{
id: "1",
first_name: "Test",
last_name: "User",
location: "Austin, TX",
photo_url: "http://www.jamkazam.com/images/users/photos/1.gif"
}
],
recordings: [
{
id: "1",
first_name: "Test",
last_name: "User",
location: "Austin, TX",
photo_url: "http://www.jamkazam.com/images/users/photos/1.gif"
}
]
}
};

View File

@ -0,0 +1,196 @@
(function(context) {
describe("searcher.js tests", function() {
describe("Empty Search", function() {
// See the markup in fixtures/searcher.htm
var searcher;
var ajaxSpy;
var fakeApp = {
ajaxError: function() {
console.debug("ajaxError");
}
};
beforeEach(function() {
loadFixtures('searcher.htm');
spyOn($, "ajax").andCallFake(function(opts) {
opts.success(TestResponses.emptySearch);
});
searcher = new JK.Searcher(fakeApp);
searcher.initialize();
});
it("No Results message shown", function() {
// Workaround for key events not being reflected in val() calls
$('.searchtextinput').val('AA');
var e = jQuery.Event("keyup");
e.which = 65; // "a"
$('.searchtextinput').focus();
$('.searchtextinput').trigger(e);
$('.searchtextinput').trigger(e);
expect($('.searchresults .emptyresult').length).toEqual(1);
});
});
describe("Full Search", function() {
// See the markup in fixtures/searcher.htm
var searcher;
var ajaxSpy;
var fakeApp = {
ajaxError: function() {
console.debug("ajaxError");
}
};
beforeEach(function() {
loadFixtures('searcher.htm');
spyOn($, "ajax").andCallFake(function(opts) {
opts.success(TestResponses.fullSearch);
});
searcher = new JK.Searcher(fakeApp);
searcher.initialize();
});
it("No Results message shown", function() {
// Workaround for key events not being reflected in val() calls
$('.searchtextinput').val('AA');
var e = jQuery.Event("keyup");
e.which = 65; // "a"
$('.searchtextinput').focus();
$('.searchtextinput').trigger(e);
$('.searchtextinput').trigger(e);
expect($('.searchresults h2').length).toEqual(4);
});
});
describe("Search Tests", function() {
// See the markup in fixtures/searcher.htm
var searcher;
var ajaxSpy;
var fakeApp = {
ajaxError: function() {
console.debug("ajaxError");
}
};
beforeEach(function() {
loadFixtures('searcher.htm');
spyOn($, "ajax").andCallFake(function(opts) {
opts.success(TestResponses.search);
});
searcher = new JK.Searcher(fakeApp);
searcher.initialize();
});
it("first keypress should not search", function() {
// Workaround for key events not being reflected in val() calls
$('.searchtextinput').val('A');
var e = jQuery.Event("keyup");
e.which = 65; // "a"
$('.searchtextinput').focus();
$('.searchtextinput').trigger(e);
expect($.ajax.wasCalled).toBe(false);
});
it("second keypress should search", function() {
$('.searchtextinput').val('AA');
$('.searchtextinput').focus();
var e = jQuery.Event("keyup");
e.which = 65; // "a"
$('.searchtextinput').trigger(e);
// trigger again
$('.searchtextinput').trigger(e);
expect($.ajax).toHaveBeenCalled();
});
it("response div is absolute position", function() {
$('.searchtextinput').val('AA');
$('.searchtextinput').focus();
var e = jQuery.Event("keyup");
e.which = 65; // "a"
$('.searchtextinput').trigger(e);
$('.searchtextinput').trigger(e);
expect($('.searchresults').css('position')).toEqual('absolute');
});
it("response displayed in results", function() {
$('.searchtextinput').val('AA');
$('.searchtextinput').focus();
var e = jQuery.Event("keyup");
e.which = 65; // "a"
$('.searchtextinput').trigger(e);
$('.searchtextinput').trigger(e);
expect($('.searchresults').length).toEqual(1);
expect($('.searchresults h2').length).toEqual(1);
expect($('.searchresults li').length).toEqual(1);
expect($('.searchresults li img').length).toEqual(1);
expect($('.searchresults li span.text').length).toEqual(1);
expect($('.searchresults li span.subtext').length).toEqual(1);
});
it("response positioned under input", function() {
$('.searchtextinput').val('AA');
$('.searchtextinput').focus();
var e = jQuery.Event("keyup");
e.which = 65; // "a"
$('.searchtextinput').trigger(e);
$('.searchtextinput').trigger(e);
expect($('.searchresults').length).toEqual(1);
var bodyOffset = $('body').offset();
var inputOffset = $('.searchtextinput').offset();
var inputHeight = $('.searchtextinput').outerHeight();
var expectedTop = bodyOffset.top + inputOffset.top + inputHeight;
var expectedLeft = bodyOffset.left + inputOffset.left;
var searchResultOffset = $('.searchresults').offset();
expect(searchResultOffset.top).toEqual(expectedTop);
expect(searchResultOffset.left).toEqual(expectedLeft);
});
it("search results are visible", function() {
$('.searchtextinput').val('AA');
$('.searchtextinput').focus();
var e = jQuery.Event("keyup");
e.which = 65; // "a"
$('.searchtextinput').trigger(e);
$('.searchtextinput').trigger(e);
var visible = $('.searchresults').is(':visible');
expect(visible).toBe(true);
});
it("escape key hides search results", function() {
$('.searchtextinput').val('AA');
$('.searchtextinput').focus();
var e = jQuery.Event("keyup");
e.which = 65; // "a"
$('.searchtextinput').trigger(e);
$('.searchtextinput').trigger(e);
e = jQuery.Event("keyup");
e.which = 27; // ESCAPE
$('.searchtextinput').trigger(e);
var visible = $('.searchresults').is(':visible');
expect(visible).toBe(false);
});
});
});
})(window);

View File

@ -0,0 +1,93 @@
require 'spec_helper'
# these tests avoid the use of ActiveRecord and FactoryGirl to do blackbox, non test-instrumented tests
describe UserManager do
before(:each) do
@user_manager = UserManager.new(:conn => @conn)
UserMailer.deliveries.clear
end
describe "signup" do
it "signup successfully" do
@user = @user_manager.signup("bob", "smith", "bob@jamkazam.com", "foobar", "foobar", "Austin", "TX", "USA", nil, "http://localhost:3000/confirm" )
@user.errors.any?.should be_false
@user.first_name.should == "bob"
@user.last_name.should == "smith"
@user.email.should == "bob@jamkazam.com"
@user.email_confirmed.should be_false
@user.city.should == "Austin"
@user.state.should == "TX"
@user.country.should == "USA"
@user.instruments.length.should == 0
@user.signup_token.should_not be_nil
UserMailer.deliveries.length.should == 1
end
it "signup successfully with instruments" do
@user = @user_manager.signup("bob", "smith", "bob@jamkazam.com", "foobar", "foobar", "Austin", "TX", "USA",
[{ :instrument_id => "electric guitar", :proficiency_level => 3, :priority => 0}], "http://localhost:3000/confirm" )
@user.errors.any?.should be_false
@user.instruments.length.should == 1
musician_instrument = @user.musician_instruments[0]
musician_instrument.instrument.should == Instrument.find("electric guitar")
musician_instrument.proficiency_level.should == 3
end
it "duplicate signup failure" do
@user = @user_manager.signup("bob", "smith", "bob@jamkazam.com", "foobar", "foobar", "Austin", "TX", "USA", nil, "http://localhost:3000/confirm" )
UserMailer.deliveries.length.should == 1
@user.errors.any?.should be_false
# exactly the same parameters; should dup on email, and send no email
@user = @user_manager.signup("bob", "smith", "bob@jamkazam.com", "foobar", "foobar", "Austin", "TX", "USA", nil, "http://localhost:3000/confirm" )
UserMailer.deliveries.length.should == 1
@user.errors.any?.should be_true
@user.errors[:email][0].should == "has already been taken"
# change email so that name appears dupped
@user = @user_manager.signup("bob", "smith", "bobbie@jamkazam.com", "foobar", "foobar", "Austin", "TX", "USA", nil, "http://localhost:3000/confirm" )
UserMailer.deliveries.length.should == 1
@user.errors.any?.should be_true
@user.errors[:name][0].should == "has already been taken"
end
it "fail on no username" do
@user = @user_manager.signup("", "", "bob@jamkazam.com", "foobar", "foobar", "Austin", "TX", "USA", nil, "http://localhost:3000/confirm" )
UserMailer.deliveries.length.should == 0
@user.errors.any?.should be_true
@user.errors[:first_name][0].should == "can't be blank"
end
it "fail on no username" do
@user = @user_manager.signup("murp", "", "foobar", "foobar", "Austin", "TX", "USA", nil, "http://localhost:3000/confirm" )
UserMailer.deliveries.length.should == 0
@user.errors.any?.should be_true
@user.errors[:email][0].should == "can't be blank"
end
end
describe "signup_confirm" do
it "fail on no username" do
@user = @user_manager.signup("bob", "smith", "bob@jamkazam.com", "foobar", "foobar", "Austin", "TX", "USA", nil, "http://localhost:3000/confirm" )
@user = @user_manager.signup_confirm(@user.signup_token)
@user.email_confirmed.should be_true
end
it "fail to confirm bogus signup token" do
@user_manager.signup_confirm("murp").should be_nil
end
it "fail to confirm empty signup token" do
@user_manager.signup_confirm("").should be_nil
end
it "fail to confirm nil signup token" do
@user_manager.signup_confirm(nil).should be_nil
end
end
end

View File

@ -28,10 +28,8 @@ describe "Search API ", :type => :api do
it "simple search" do
User.delete_search_index # so that the user created before the test and logged in doesn't show up
User.create_search_index
@musician = User.save(name: "Peach", email: "user@example.com",
password: "foobar", password_confirmation: "foobar", musician: true)
@fan = User.save(name: "Peach Peach", email: "fan@example.com",
password: "foobar", password_confirmation: "foobar", musician: false)
@musician = FactoryGirl.create(:user, first_name: "Peach", last_name: "Nothing", name: "peach1", email: "user@example.com", musician: true)
@fan = FactoryGirl.create(:user, first_name: "Peach Peach", last_name: "Grovery", name: "peach2", email: "fan@example.com", musician: false)
@band = Band.save(name: "Peach pit", website: "www.bands.com", biography: "zomg we rock")
@band2 = Band.save(name: "Peach", website: "www.bands2.com", biography: "zomg we rock")
User.search_index.refresh

View File

@ -114,6 +114,9 @@ describe "User pages" do
before do
fill_in "Name", with: "Example User"
fill_in "Email", with: "user@example.com"
fill_in "City", with: "Austin"
fill_in "State", with: "TX"
fill_in "Country", with: "USA"
fill_in "Password", with: "foobar"
fill_in "Confirmation", with: "foobar"
end
@ -127,9 +130,8 @@ describe "User pages" do
let(:user) { User.find_by_email('user@example.com') }
it { should have_selector('title', text: user.name) }
it { should have_selector('div.alert.alert-success', text: 'Welcome') }
it { should have_link('Sign out') }
it { should have_selector('title', text: "Confirmation Email Sent") }
it { should have_selector('div.alert.alert-success', text: 'check your email') }
end
end
end

View File

@ -0,0 +1,50 @@
require 'spec_helper'
describe "User API ", :type => :api do
include Rack::Test::Methods
subject { page }
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
before(:each) do
UserMailer.deliveries.clear
end
it "successful login" do
# can't access most apis; not logged in yet!'
get '/api/users.json', "CONTENT_TYPE" => 'application/json'
last_response.status.should == 403
# login
post '/api/auth_sessions.json', { :email => user.email, :password => user.password }.to_json, "CONTENT_TYPE" => 'application/json'
last_response.status.should == 200
JSON.parse(last_response.body).should == { "success" => true }
# can now login
get '/api/users.json', "CONTENT_TYPE" => 'application/json'
last_response.status.should == 200
# log back out
delete '/api/auth_sessions.json', "CONTENT_TYPE" => 'application/json'
# can't access most apis; not logged in yet!'
get '/api/users.json', "CONTENT_TYPE" => 'application/json'
last_response.status.should == 403
end
it "bad login" do
# login
post '/api/auth_sessions.json', { :email => "nothing", :password => "mur" }.to_json, "CONTENT_TYPE" => 'application/json'
last_response.status.should == 404
JSON.parse(last_response.body).should == { "success" => false }
# can't access most apis; not logged in yet!'
get '/api/users.json', "CONTENT_TYPE" => 'application/json'
last_response.status.should == 403
end
end
end

View File

@ -4,11 +4,15 @@ require 'spork'
#require 'spork/ext/ruby-debug'
require 'active_record'
require 'action_mailer'
require 'jam_db'
require 'spec_db'
include JamRuby
# put ActionMailer into test mode
ActionMailer::Base.delivery_method = :test
# recreate test database and migrate it
db_config = YAML::load(File.open('config/database.yml'))["test"]
SpecDb::recreate_database(db_config)