fingerrails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.gitignore +3 -0
  2. data/README.rdoc +3 -0
  3. data/Rakefile +28 -0
  4. data/VERSION +1 -0
  5. data/bin/fingerrails +9 -0
  6. data/fingerrails.gemspec +90 -0
  7. data/fingertips.rb +181 -0
  8. data/templates/.kick +22 -0
  9. data/templates/Rakefile +23 -0
  10. data/templates/app/controllers/application_controller.rb +51 -0
  11. data/templates/app/controllers/members_controller.rb +32 -0
  12. data/templates/app/controllers/passwords_controller.rb +46 -0
  13. data/templates/app/controllers/sessions_controller.rb +26 -0
  14. data/templates/app/helpers/application_helper.rb +20 -0
  15. data/templates/app/models/mailer.rb +8 -0
  16. data/templates/app/models/member.rb +10 -0
  17. data/templates/app/models/member/authentication.rb +44 -0
  18. data/templates/app/views/layouts/_application_javascript_includes.html.erb +3 -0
  19. data/templates/app/views/layouts/_head.html.erb +3 -0
  20. data/templates/app/views/layouts/application.html.erb +25 -0
  21. data/templates/app/views/mailer/reset_password_message.erb +8 -0
  22. data/templates/app/views/members/edit.html.erb +21 -0
  23. data/templates/app/views/members/new.html.erb +26 -0
  24. data/templates/app/views/members/show.html.erb +3 -0
  25. data/templates/app/views/passwords/edit.html.erb +22 -0
  26. data/templates/app/views/passwords/new.html.erb +22 -0
  27. data/templates/app/views/passwords/reset.html.erb +9 -0
  28. data/templates/app/views/passwords/sent.html.erb +11 -0
  29. data/templates/app/views/sessions/_form.html.erb +22 -0
  30. data/templates/app/views/sessions/_status.html.erb +9 -0
  31. data/templates/app/views/sessions/new.html.erb +5 -0
  32. data/templates/config/database.yml +18 -0
  33. data/templates/lib/active_record_ext.rb +26 -0
  34. data/templates/lib/token.rb +9 -0
  35. data/templates/public/403.html +29 -0
  36. data/templates/public/javascripts/ready.js +9 -0
  37. data/templates/public/stylesheets/default.css +143 -0
  38. data/templates/public/stylesheets/reset.css +52 -0
  39. data/templates/test/ext/authentication.rb +24 -0
  40. data/templates/test/ext/file_fixtures.rb +8 -0
  41. data/templates/test/ext/time.rb +8 -0
  42. data/templates/test/fixtures/members.yml +8 -0
  43. data/templates/test/functional/application_controller_test.rb +104 -0
  44. data/templates/test/functional/members_controller_test.rb +71 -0
  45. data/templates/test/functional/passwords_controller_test.rb +95 -0
  46. data/templates/test/functional/sessions_controller_test.rb +68 -0
  47. data/templates/test/lib/active_record_ext_test.rb +13 -0
  48. data/templates/test/lib/token_test.rb +17 -0
  49. data/templates/test/test_helper.rb +32 -0
  50. data/templates/test/unit/helpers/application_helper_test.rb +44 -0
  51. data/templates/test/unit/mailer_test.rb +13 -0
  52. data/templates/test/unit/member/authentication_test.rb +73 -0
  53. data/templates/test/unit/member_test.rb +32 -0
  54. metadata +108 -0
@@ -0,0 +1,9 @@
1
+ // Let the javascript libs start their behaviour. These are all included at the
2
+ // end of the document so the browser will first render the document before
3
+ // requesting the javascripts.
4
+
5
+ // Event.observe(document, 'dom:loaded', function() {
6
+ // });
7
+
8
+ // Event.observe(window, 'load', function() {
9
+ // });
@@ -0,0 +1,143 @@
1
+ /* @group Typography */
2
+
3
+ html,
4
+ input
5
+ {
6
+ font: 14px/20px "Lucida Grande", Lucida, "Lucida Sans Unicode", Verdana, sans-serif;
7
+ }
8
+
9
+ h1
10
+ {
11
+ font-size: 200%;
12
+ }
13
+ h2
14
+ {
15
+ font-size: 150%;
16
+ margin-bottom: 24px;
17
+ }
18
+
19
+ /* @end */
20
+
21
+ /* @group Basics */
22
+
23
+ a,
24
+ a:visited
25
+ {
26
+ color: #689051;
27
+ }
28
+
29
+ a:hover,
30
+ a:active
31
+ {
32
+ color: #84b567;
33
+ }
34
+
35
+ /* @end */
36
+
37
+ /* @group Layout */
38
+
39
+ a#logo
40
+ {
41
+ display: block;
42
+ padding: 6px 12px;
43
+ color: #999;
44
+ text-decoration: none;
45
+ }
46
+
47
+ p#member
48
+ {
49
+ position: absolute;
50
+ top: 3px;
51
+ right: 12px;
52
+ }
53
+ p#member a
54
+ {
55
+ text-decoration: none;
56
+ font-size: 10px;
57
+ }
58
+ p#member a+a
59
+ {
60
+ margin-left: 6px;
61
+ }
62
+
63
+ ul#navigation
64
+ {
65
+ background-color: #bdff94;
66
+ padding: 6px 12px;
67
+ margin-bottom: 12px;
68
+ border-top: 1px solid #bfff97;
69
+ border-bottom: 1px solid #a0da7d;
70
+ }
71
+ ul#navigation li
72
+ {
73
+ display: inline;
74
+ }
75
+ ul#navigation a,
76
+ ul#navigation a:visited
77
+ {
78
+ text-decoration: none;
79
+ color: #222;
80
+ }
81
+ ul#navigation a:hover,
82
+ ul#navigation a:active
83
+ {
84
+ color: #7d9b1d;
85
+ }
86
+ ul#navigation li + li
87
+ {
88
+ margin-left: 6px;
89
+ }
90
+
91
+ div#main
92
+ {
93
+ padding: 12px;
94
+ }
95
+
96
+ /* @end */
97
+
98
+ /* @group Forms */
99
+
100
+ div.field
101
+ {
102
+ margin: 12px 0px;
103
+ }
104
+ label,
105
+ div.label
106
+ {
107
+ color: #666;
108
+ }
109
+ div.label a
110
+ {
111
+ color: #999;
112
+ font-size: 80%;
113
+ text-decoration: none;
114
+ }
115
+ div.label a:hover
116
+ {
117
+ text-decoration: underline;
118
+ }
119
+ div.inline label
120
+ {
121
+ font-size: 90%;
122
+ }
123
+
124
+ input[type="text"],
125
+ input[type="password"]
126
+ {
127
+ color: #222;
128
+ }
129
+
130
+ a.cancel
131
+ {
132
+ text-decoration: none;
133
+ color: #999;
134
+ font-size: 80%;
135
+ margin-left: 3px;
136
+ }
137
+ a.cancel:hover
138
+ {
139
+ text-decoration: underline;
140
+ }
141
+
142
+ /* @end */
143
+
@@ -0,0 +1,52 @@
1
+ /* Reset, based on http://meyerweb.com/eric/tools/css/reset/ */
2
+
3
+ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, button, legend, table, caption, tbody, tfoot, thead, tr, th, td
4
+ {
5
+ margin: 0;
6
+ padding: 0;
7
+ border: 0;
8
+ outline: 0;
9
+ font-size: 100%;
10
+ vertical-align: baseline;
11
+ background: transparent;
12
+ }
13
+ body
14
+ {
15
+ line-height: 1;
16
+ }
17
+ ol, ul
18
+ {
19
+ list-style: none;
20
+ }
21
+ blockquote, q
22
+ {
23
+ quotes: none;
24
+ }
25
+ blockquote:before, blockquote:after, q:before, q:after
26
+ {
27
+ content: '';
28
+ content: none;
29
+ }
30
+ :focus /* remember to define focus styles! */
31
+ {
32
+ outline: 0;
33
+ }
34
+ ins /* remember to highlight inserts somehow! */
35
+ {
36
+ text-decoration: none;
37
+ }
38
+ del
39
+ {
40
+ text-decoration: line-through;
41
+ }
42
+ table /* tables still need 'cellspacing="0"' in the markup */
43
+ {
44
+ border-collapse: collapse;
45
+ border-spacing: 0;
46
+ }
47
+ hr
48
+ {
49
+ height: 1px;
50
+ background-color: #000;
51
+ border: none;
52
+ }
@@ -0,0 +1,24 @@
1
+ module TestHelpers
2
+ module Authentication
3
+ def login(member, password='secret')
4
+ @authenticated = member
5
+ request.session[:member_id] = @authenticated.id
6
+ end
7
+
8
+ def logout
9
+ request.session.delete(:member_id)
10
+ end
11
+
12
+ def authenticated?
13
+ !request.session[:member_id].blank?
14
+ end
15
+
16
+ def access_denied?
17
+ response.status.to_i == 403
18
+ end
19
+
20
+ def login_required?
21
+ response.header['Location'] == new_session_url
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ module TestHelpers
2
+ module FileFixtures
3
+ def fixture_file_upload(path, mime_type = nil, binary = false)
4
+ fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path)
5
+ ActionController::TestUploadedFile.new("#{fixture_path}#{path}", mime_type, binary)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module TestHelpers
2
+ module Time
3
+ def freeze_time!(time = ::Time.parse('6/1/2009'))
4
+ ::Time.stubs(:now).returns(time)
5
+ time
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ adrian:
2
+ hashed_password: <%= Member.hash_password('secret') %>
3
+ email: adrian@example.com
4
+ role: member
5
+ kelly:
6
+ hashed_password: <%= Member.hash_password('secret') %>
7
+ email: kelly@example.com
8
+ role: member
@@ -0,0 +1,104 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class TestApplicationsController < ApplicationController
4
+ allow_access :admin, :only => :private_action
5
+ allow_access :all, :except => :private_action
6
+
7
+ def private_action
8
+ render :nothing => true
9
+ end
10
+
11
+ def private_action_with_flash
12
+ flash[:notice] = 'Message'
13
+ render :nothing => true
14
+ end
15
+
16
+ def public_action
17
+ render :nothing => true
18
+ end
19
+
20
+ def action_with_layout
21
+ render :inline => '', :layout => 'application'
22
+ end
23
+
24
+ def action_with_respond_block
25
+ respond_to do |format|
26
+ format.html { render :text => 'HTML' }
27
+ format.jpg { render :text => 'JPG'}
28
+ end
29
+ end
30
+ end
31
+
32
+ ActionController::Routing::Routes.draw do |map|
33
+ map.resource :test_application, :member => {
34
+ :private_action => :get,
35
+ :private_action_with_flash => :get,
36
+ :public_action => :get,
37
+ :action_with_layout => :get,
38
+ :action_with_respond_block => :get
39
+ }
40
+ end
41
+
42
+ describe TestApplicationsController do
43
+ it "should find the currently logged in member" do
44
+ login members(:adrian)
45
+ get :public_action
46
+ assigns(:authenticated).should == members(:adrian)
47
+ end
48
+
49
+ it "should not find currently logged in member when no-one is logged in" do
50
+ get :public_action
51
+ assigns(:authenticated).should.be.blank
52
+ end
53
+
54
+ it "should log a member in" do
55
+ should.not.be.authenticated
56
+ @controller.send(:login, members(:adrian))
57
+ should.be.authenticated
58
+ end
59
+
60
+ it "should log a member out" do
61
+ @controller.send(:login, members(:adrian))
62
+ should.be.authenticated
63
+ @controller.send(:logout)
64
+ should.not.be.authenticated
65
+ end
66
+
67
+ it "should respond with HTML before JPG" do
68
+ request.env['HTTP_ACCEPT'] = "*/*"
69
+ get :action_with_respond_block
70
+ response.body.should == 'HTML'
71
+ end
72
+
73
+ it "should respond with HTML before JPG, even with IE" do
74
+ request.env["HTTP_USER_AGENT"] = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Win64; x64)'
75
+ request.env['HTTP_ACCEPT'] = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/x-shockwave-flash, application/x-silverlight, */*"
76
+ get :action_with_respond_block
77
+ response.body.should == 'HTML'
78
+ end
79
+ end
80
+
81
+ describe TestApplicationsController, "when dealing with pages that need authorization" do
82
+ it "should allow the user to authorize" do
83
+ get :private_action
84
+ should.redirect_to new_session_url
85
+ end
86
+
87
+ it "should keep the flash around when access was refused" do
88
+ get :private_action_with_flash
89
+ flash[:notice].should == 'Message'
90
+ end
91
+
92
+ it "should have stored the url to return the user to after authentication" do
93
+ get :private_action
94
+ controller.after_authentication[:redirect_to].should == controller.url_for(:action => :private_action)
95
+ end
96
+
97
+ it "should show a forbidden page when access was refused" do
98
+ controller.stubs(:find_authenticated)
99
+ controller.send(:instance_variable_set, "@authenticated", stub(:id => 1, :role => 'member'))
100
+
101
+ get :private_action
102
+ status.should.be :forbidden
103
+ end
104
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe "On the", MembersController, "a visitor" do
4
+ it "should see a form for a new member" do
5
+ get :new
6
+ status.should.be :ok
7
+ template.should.be 'members/new'
8
+ assert_select 'form'
9
+ end
10
+
11
+ it "should create a new member" do
12
+ lambda {
13
+ post :create, :member => valid_params
14
+ }.should.differ('Member.count', +1)
15
+ assigns(:member).email.should == valid_params[:email]
16
+ assigns(:member).hashed_password.should == Member.hash_password(valid_params[:password])
17
+ should.redirect_to root_url
18
+ should.be.authenticated
19
+ end
20
+
21
+ it "should show validation errors after a failed create" do
22
+ post :create, :member => valid_params.merge(:email => '')
23
+ status.should.be :ok
24
+ template.should.be 'members/new'
25
+ assert_select 'div.errorExplanation'
26
+ assert_select 'form'
27
+ should.not.be.authenticated
28
+ end
29
+
30
+ it "should see a profile" do
31
+ get :show, :id => members(:adrian)
32
+ assigns(:member).should == members(:adrian)
33
+ status.should.be :success
34
+ template.should.be 'members/show'
35
+ end
36
+
37
+ should.require_login.get :edit, :id => members(:adrian)
38
+ should.require_login.put :update, :id => members(:adrian)
39
+ should.require_login.delete :destroy, :id => members(:adrian)
40
+
41
+ private
42
+
43
+ def valid_params
44
+ { :email => 'jurgen@example.com', :password => 'so secret', :verify_password => 'so secret' }
45
+ end
46
+ end
47
+
48
+ describe "On the", MembersController, "a member" do
49
+ before do
50
+ login members(:adrian)
51
+ end
52
+
53
+ it "should see an edit form" do
54
+ get :edit, :id => @authenticated.to_param
55
+
56
+ assert_select 'form'
57
+ assigns(:member).should == @authenticated
58
+ status.should.be :success
59
+ template.should.be 'members/edit'
60
+ end
61
+
62
+ it "should be able to update his profile" do
63
+ put :update, :id => @authenticated.to_param, :member => { :email => 'sir.adrian@example.com' }
64
+
65
+ @authenticated.reload.email.should == 'sir.adrian@example.com'
66
+ should.redirect_to member_url(@authenticated)
67
+ end
68
+
69
+ should.disallow.put :update, :id => members(:kelly)
70
+ should.disallow.delete :destroy, :id => members(:kelly)
71
+ end
@@ -0,0 +1,95 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe "On the", PasswordsController, "a visitor" do
4
+ before do
5
+ @member = members(:adrian)
6
+ end
7
+
8
+ it "should see a new form to reset his password" do
9
+ get :new
10
+ status.should.be :success
11
+ template.should.be 'passwords/new'
12
+ assert_select 'form'
13
+ end
14
+
15
+ it "should generate a password reset token and send it via email" do
16
+ @member.update_attribute(:reset_password_token, nil)
17
+
18
+ assert_emails(1) do
19
+ post :create, :email => @member.email
20
+ end
21
+
22
+ assigns(:member).should == @member
23
+ @member.reload.reset_password_token.should.not.be.blank
24
+
25
+ email = ActionMailer::Base.deliveries.last
26
+ email.body.should.include edit_password_url(:id => @member.reset_password_token)
27
+
28
+ status.should.be :success
29
+ template.should.be 'passwords/sent'
30
+ end
31
+
32
+ it "should see an error if no member can be found for a given email address" do
33
+ assert_emails(0) do
34
+ post :create, :email => "joe.the.plumber@example.com"
35
+ end
36
+
37
+ status.should.be :ok
38
+ template.should.be 'passwords/new'
39
+ flash[:error].should.not.be.blank
40
+ end
41
+
42
+ it "should see a warning if an SMTP error occurred" do
43
+ Mailer.stubs(:deliver_reset_password_message).raises Net::SMTPFatalError
44
+ post :create, :email => @member.email
45
+
46
+ status.should.be :ok
47
+ template.should.be 'passwords/new'
48
+ flash[:error].should.not.be.blank
49
+ end
50
+
51
+ it "should see an edit form for his password" do
52
+ @member.generate_reset_password_token!
53
+ get :edit, :id => @member.reset_password_token
54
+
55
+ status.should.be :success
56
+ assigns(:member).should == @member
57
+ template.should.be 'passwords/edit'
58
+ assert_select 'form'
59
+ end
60
+
61
+ it "should not see an edit form if no member is found for the given token" do
62
+ get :edit, :id => "doesnotexist"
63
+
64
+ status.should.be :not_found
65
+ assigns(:member).should.be nil
66
+ end
67
+
68
+ it "should be able to update his password" do
69
+ @member.generate_reset_password_token!
70
+ put :update, :id => @member.reset_password_token, :password => "newpass"
71
+
72
+ Member.authenticate(:email => @member.email, :password => "newpass").should == @member
73
+ status.should.be :success
74
+ template.should.be 'passwords/reset'
75
+ end
76
+
77
+ it "should see an error if the password is empty" do
78
+ @member.generate_reset_password_token!
79
+ before = @member.hashed_password
80
+
81
+ put :update, :id => @member.reset_password_token, :password => ""
82
+
83
+ @member.reload.hashed_password.should == before
84
+ status.should.be :success
85
+ template.should.be 'passwords/edit'
86
+ assert_select "div.errorExplanation", :text => "The password can’t be blank."
87
+ end
88
+
89
+ it "should not be allowed to update a password with an incorrect token" do
90
+ put :update, :id => "doesnotexist", :password => "newpass"
91
+
92
+ status.should.be :not_found
93
+ assigns(:member).should.be nil
94
+ end
95
+ end