fingerrails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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