rails_blog_engine 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +24 -0
  4. data/Guardfile +21 -0
  5. data/Procfile +1 -0
  6. data/TODO.txt +31 -0
  7. data/app/assets/images/rails_blog_engine/.gitkeep +0 -0
  8. data/app/controllers/rails_blog_engine/comments_controller.rb +1 -1
  9. data/app/controllers/rails_blog_engine/posts_controller.rb +1 -1
  10. data/lib/rails_blog_engine.rb +2 -0
  11. data/lib/rails_blog_engine/version.rb +1 -1
  12. data/rails_blog_engine.gemspec +64 -0
  13. data/script/rails +6 -0
  14. data/spec/acceptance/acceptance_helper.rb +4 -0
  15. data/spec/acceptance/rails_blog_engine/posts_admin_spec.rb +33 -0
  16. data/spec/acceptance/rails_blog_engine/posts_spec.rb +74 -0
  17. data/spec/acceptance/rails_blog_engine/spam_filtering_spec.rb +110 -0
  18. data/spec/acceptance/support/helpers.rb +18 -0
  19. data/spec/acceptance/support/paths.rb +5 -0
  20. data/spec/controllers/rails_blog_engine/comments_controller_spec.rb +22 -0
  21. data/spec/controllers/rails_blog_engine/posts_controller_spec.rb +81 -0
  22. data/spec/dummy/Rakefile +7 -0
  23. data/spec/dummy/app/assets/javascripts/application.js +10 -0
  24. data/spec/dummy/app/assets/stylesheets/application.css +8 -0
  25. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  26. data/spec/dummy/app/controllers/home_controller.rb +4 -0
  27. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  28. data/spec/dummy/app/mailers/.gitkeep +0 -0
  29. data/spec/dummy/app/models/.gitkeep +0 -0
  30. data/spec/dummy/app/models/ability.rb +11 -0
  31. data/spec/dummy/app/models/user.rb +9 -0
  32. data/spec/dummy/app/views/home/index.html.haml +2 -0
  33. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  34. data/spec/dummy/config.ru +4 -0
  35. data/spec/dummy/config/application.rb +54 -0
  36. data/spec/dummy/config/boot.rb +10 -0
  37. data/spec/dummy/config/database.yml +25 -0
  38. data/spec/dummy/config/environment.rb +5 -0
  39. data/spec/dummy/config/environments/development.rb +30 -0
  40. data/spec/dummy/config/environments/production.rb +60 -0
  41. data/spec/dummy/config/environments/test.rb +42 -0
  42. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  43. data/spec/dummy/config/initializers/devise.rb +211 -0
  44. data/spec/dummy/config/initializers/inflections.rb +10 -0
  45. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  46. data/spec/dummy/config/initializers/rails_blog_engine.rb +13 -0
  47. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  48. data/spec/dummy/config/initializers/session_store.rb +8 -0
  49. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  50. data/spec/dummy/config/locales/devise.en.yml +58 -0
  51. data/spec/dummy/config/locales/en.yml +5 -0
  52. data/spec/dummy/config/routes.rb +6 -0
  53. data/spec/dummy/db/migrate/20110913113004_devise_create_users.rb +28 -0
  54. data/spec/dummy/db/schema.rb +67 -0
  55. data/spec/dummy/lib/assets/.gitkeep +0 -0
  56. data/spec/dummy/log/.gitkeep +0 -0
  57. data/spec/dummy/public/404.html +26 -0
  58. data/spec/dummy/public/422.html +26 -0
  59. data/spec/dummy/public/500.html +26 -0
  60. data/spec/dummy/public/favicon.ico +0 -0
  61. data/spec/dummy/script/rails +6 -0
  62. data/spec/helpers/rails_blog_engine/application_helper_spec.rb +56 -0
  63. data/spec/helpers/rails_blog_engine/comments_helper_spec.rb +14 -0
  64. data/spec/helpers/rails_blog_engine/posts_helper_spec.rb +14 -0
  65. data/spec/lib/generators/rails_blog_engine/install_generator_spec.rb +78 -0
  66. data/spec/lib/rails_blog_engine/filters/base_spec.rb +13 -0
  67. data/spec/lib/rails_blog_engine/filters/code_spec.rb +16 -0
  68. data/spec/lib/rails_blog_engine/filters_spec.rb +58 -0
  69. data/spec/models/rails_blog_engine/comment_spec.rb +84 -0
  70. data/spec/models/rails_blog_engine/post_spec.rb +121 -0
  71. data/spec/spec_helper.rb +36 -0
  72. data/spec/support/blueprints.rb +25 -0
  73. data/spec/support/javascript.rb +27 -0
  74. data/spec/support/vcr.rb +8 -0
  75. data/spec/vcr_cassettes/rakismet-ham.yml +30 -0
  76. data/spec/vcr_cassettes/rakismet-spam.yml +30 -0
  77. data/spec/vcr_cassettes/rakismet-train-as-ham.yml +32 -0
  78. data/spec/vcr_cassettes/rakismet-train-as-spam.yml +32 -0
  79. metadata +175 -101
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
24
+ </div>
25
+ </body>
26
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::ApplicationHelper do
4
+ class SillyFilter < RailsBlogEngine::Filters::Base
5
+ register_filter :silly
6
+
7
+ def process(text, options)
8
+ "Whee"
9
+ end
10
+ end
11
+
12
+ describe ".markdown" do
13
+ it "converts markdown text to HTML" do
14
+ helper.markdown("_foo_ bar").should match(/<em>foo<\/em> bar/)
15
+ end
16
+
17
+ it "does not pass scripts" do
18
+ helper.markdown("<script>foo</script>").
19
+ should_not match(/script/)
20
+ end
21
+
22
+ it "applies filters" do
23
+ helper.markdown("<filter:silly/>").should match(/Whee/)
24
+ end
25
+
26
+ context "for trusted users" do
27
+ it "allows images" do
28
+ helper.markdown("<img src='foo.png'>", :trusted? => true).
29
+ should match(/<img/)
30
+ end
31
+
32
+ it "does not add nofollow to links" do
33
+ helper.markdown("<a href='foo.html'>", :trusted? => true).
34
+ should_not match(/nofollow/)
35
+ end
36
+
37
+ it "allows code highlighting" do
38
+ formatted = helper.markdown(<<EOD, :trusted? => true)
39
+ <div class='foo'><span class='bar'></span></div>
40
+ EOD
41
+ formatted.should match(/foo/)
42
+ formatted.should match(/bar/)
43
+ end
44
+ end
45
+
46
+ context "for untrusted users" do
47
+ it "does not allow images" do
48
+ helper.markdown("<img src='foo.png'>").should_not match(/<img/)
49
+ end
50
+
51
+ it "adds nofollow to links" do
52
+ helper.markdown("<a href='foo.html'>").should match(/nofollow/)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ # Specs in this file have access to a helper object that includes
4
+ # the CommentsHelper. For example:
5
+ #
6
+ # describe CommentsHelper do
7
+ # describe "string concat" do
8
+ # it "concats two strings with spaces" do
9
+ # helper.concat_strings("this","that").should == "this that"
10
+ # end
11
+ # end
12
+ # end
13
+ describe RailsBlogEngine::CommentsHelper do
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ # Specs in this file have access to a helper object that includes
4
+ # the PostsHelper. For example:
5
+ #
6
+ # describe PostsHelper do
7
+ # describe "string concat" do
8
+ # it "concats two strings with spaces" do
9
+ # helper.concat_strings("this","that").should == "this that"
10
+ # end
11
+ # end
12
+ # end
13
+ describe RailsBlogEngine::PostsHelper do
14
+ end
@@ -0,0 +1,78 @@
1
+ require "spec_helper.rb"
2
+ require "generator_spec/test_case"
3
+ require "generators/rails_blog_engine/install/install_generator"
4
+
5
+ describe RailsBlogEngine::InstallGenerator do
6
+ include GeneratorSpec::TestCase
7
+ destination File.expand_path("../../../../tmp", __FILE__)
8
+
9
+ # Create a file with the specified contents in our fake application tree.
10
+ # This is faster than generating a real application every time we test.
11
+ def create_file(path, contents)
12
+ full_path = ::File.join(destination_root, path)
13
+ mkdir_p(::File.dirname(full_path))
14
+ ::File.open(full_path, 'w') {|f| f.write(contents) }
15
+ end
16
+
17
+ def prepare_destination
18
+ super
19
+ create_file('config/routes.rb', <<EOD)
20
+ Dummy::Application.routes.draw do
21
+ end
22
+ EOD
23
+ create_file('app/assets/stylesheets/application.css', <<EOD)
24
+ /*
25
+ *= require_self
26
+ *= require_tree .
27
+ */
28
+ EOD
29
+ create_file('app/assets/javascripts/application.js', <<EOD)
30
+ //= require jquery
31
+ //= require jquery_ujs
32
+ //= require_tree .
33
+ EOD
34
+ end
35
+
36
+ before do
37
+ prepare_destination
38
+ run_generator
39
+ end
40
+
41
+ it "generates the expected files" do
42
+ destination_root.should have_structure {
43
+ directory "app" do
44
+ directory "assets" do
45
+ directory "javascripts" do
46
+ file "application.js" do
47
+ contains "//= require rails_blog_engine"
48
+ end
49
+ end
50
+ directory "stylesheets" do
51
+ file "application.css" do
52
+ contains " *= require rails_blog_engine"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ directory "config" do
58
+ file "routes.rb" do
59
+ contains 'mount RailsBlogEngine::Engine => "/blog"'
60
+ end
61
+ directory "initializers" do
62
+ file "rails_blog_engine.rb"
63
+ end
64
+ directory "locales" do
65
+ file "rails_blog_engine.en.yml"
66
+ end
67
+ end
68
+ directory "db" do
69
+ directory "migrate" do
70
+ # We don't need to test every single one of these--just make sure
71
+ # a couple are copied over, and the rest should be OK.
72
+ file "20110912153527_create_rails_blog_engine_posts.rb"
73
+ file "20110913190319_add_fields_to_rails_blog_engine_post.rb"
74
+ end
75
+ end
76
+ }
77
+ end
78
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::Filters::Base do
4
+ let(:filter) { RailsBlogEngine::Filters::Base.new }
5
+
6
+ describe "#process" do
7
+ it "raises an error if not overridden" do
8
+ lambda do
9
+ filter.process("text", {})
10
+ end.should raise_error(/override/)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::Filters::Code do
4
+ let(:filter) { RailsBlogEngine::Filters::Code.new }
5
+
6
+ describe ".process" do
7
+ it "applies syntax highlighting to code blocks" do
8
+ output = filter.process(<<END_OF_CODE, :lang => 'ruby')
9
+ def foo
10
+ 42
11
+ end
12
+ END_OF_CODE
13
+ output.should match(/foo/)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::Filters do
4
+
5
+ # A sample filter.
6
+ class HelloFilter < RailsBlogEngine::Filters::Base
7
+ register_filter :hello
8
+
9
+ def process(text, options)
10
+ attrs =
11
+ if options[:class] then " class=\"#{options[:class]}\"" else "" end
12
+ name = if text then ", #{text}" else "" end
13
+ "<p#{attrs}>Hello#{name}!</p>"
14
+ end
15
+ end
16
+
17
+ describe ".find" do
18
+ it "returns the filter registered for a name" do
19
+ RailsBlogEngine::Filters.find(:hello).should be_kind_of(HelloFilter)
20
+ end
21
+ end
22
+
23
+ describe ".apply_all_to" do
24
+ %w(filter macro typo).each do |tag|
25
+ it "applies registered filters to empty '#{tag}' tags" do
26
+ RailsBlogEngine::Filters.apply_all_to(<<"END_OF_INPUT").should == <<END_OF_OUTPUT
27
+ <#{tag}:hello/>
28
+ <#{tag}:hello class="example" />
29
+ END_OF_INPUT
30
+ <p>Hello!</p>
31
+ <p class="example">Hello!</p>
32
+ END_OF_OUTPUT
33
+ end
34
+
35
+ it "applies registered filters to '#{tag}' tags with content" do
36
+ RailsBlogEngine::Filters.apply_all_to(<<"END_OF_INPUT").should == <<END_OF_OUTPUT
37
+ <#{tag}:hello>Judy</#{tag}:hello>
38
+ <#{tag}:hello class='example' extra="" >Mike
39
+ Smith</#{tag}:hello>
40
+ END_OF_INPUT
41
+ <p>Hello, Judy!</p>
42
+ <p class="example">Hello, Mike
43
+ Smith!</p>
44
+ END_OF_OUTPUT
45
+ end
46
+ end
47
+
48
+ it "reports errors inline" do
49
+ RailsBlogEngine::Filters.apply_all_to(<<END_OF_INPUT).should == <<END_OF_OUTPUT
50
+ <filter:invalid/>
51
+ <filter:hello class= >Mike</filter:hello>
52
+ END_OF_INPUT
53
+ <p><strong>Text filter not installed: invalid</strong></p>
54
+ <p><strong>Can't parse filter arguments: {{ class= }}</strong></p>
55
+ END_OF_OUTPUT
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::Comment do
4
+ it { should belong_to(:post) }
5
+
6
+ it { should_not allow_value('').for(:author_byline) }
7
+ it { should_not allow_value('').for(:body) }
8
+
9
+ describe ".visible" do
10
+ it "includes unfiltered and ham messages, but not spam" do
11
+ @unfiltered = RailsBlogEngine::Comment.make!
12
+ @ham = RailsBlogEngine::Comment.make!(:state => 'filtered_as_ham')
13
+ @ham2 = RailsBlogEngine::Comment.make!(:state => 'marked_as_ham')
14
+ @spam = RailsBlogEngine::Comment.make!(:state => 'filtered_as_spam')
15
+ @spam2 = RailsBlogEngine::Comment.make!(:state => 'marked_as_spam')
16
+ RailsBlogEngine::Comment.visible.should include(@unfiltered)
17
+ RailsBlogEngine::Comment.visible.should include(@ham)
18
+ RailsBlogEngine::Comment.visible.should include(@ham2)
19
+ RailsBlogEngine::Comment.visible.should_not include(@spam)
20
+ RailsBlogEngine::Comment.visible.should_not include(@spam2)
21
+ end
22
+ end
23
+
24
+ describe "#state" do
25
+ subject { RailsBlogEngine::Comment.make! }
26
+
27
+ def enable_spam_filter
28
+ Rakismet.key = "fakekey"
29
+ end
30
+
31
+ def disable_spam_filter
32
+ Rakismet.key = nil
33
+ end
34
+
35
+ it "begins in state unfiltered" do
36
+ subject.should be_unfiltered
37
+ end
38
+
39
+ it "transitions to filtered_as_ham if rakismet likes it" do
40
+ enable_spam_filter
41
+ subject.stub(:spam?) { false }
42
+ subject.run_spam_filter
43
+ subject.should be_filtered_as_ham
44
+ end
45
+
46
+ it "transitions to filtered_as_spam if rakismet doesn't like it" do
47
+ enable_spam_filter
48
+ subject.stub(:spam?) { true }
49
+ subject.run_spam_filter
50
+ subject.should be_filtered_as_spam
51
+ end
52
+
53
+ it "remains unfiltered if the rakismet is not configured" do
54
+ disable_spam_filter
55
+ subject.run_spam_filter
56
+ subject.should be_unfiltered
57
+ end
58
+
59
+ it "supports manually marking a filtered post as ham" do
60
+ enable_spam_filter
61
+ subject.filter_as_spam
62
+ subject.can_mark_as_ham?.should == true
63
+ subject.should_receive(:ham!)
64
+ subject.mark_as_ham
65
+ subject.should be_marked_as_ham
66
+ end
67
+
68
+ it "supports manually marking a filtered post as spam" do
69
+ enable_spam_filter
70
+ subject.filter_as_ham
71
+ subject.can_mark_as_spam?.should == true
72
+ subject.should_receive(:spam!)
73
+ subject.mark_as_spam
74
+ subject.should be_marked_as_spam
75
+ end
76
+
77
+ it "supports manually marking an unfiltered post as spam" do
78
+ disable_spam_filter
79
+ subject.can_mark_as_spam?.should == true
80
+ subject.mark_as_spam
81
+ subject.should be_marked_as_spam
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::Post do
4
+ Post = RailsBlogEngine::Post
5
+
6
+ it { should have_many(:comments) }
7
+
8
+ describe "validations" do
9
+ before { Post.make! }
10
+
11
+ it { should allow_value("Title").for(:title) }
12
+ it { should_not allow_value("").for(:title) }
13
+
14
+ it { should allow_value("Body").for(:body) }
15
+ it { should_not allow_value("").for(:body) }
16
+
17
+ %w(unpublished published).each do |state|
18
+ it { should allow_value(state).for(:state) }
19
+ end
20
+ it { should_not allow_value("invalid").for(:state) }
21
+ it { should validate_presence_of(:state) }
22
+
23
+ it { should allow_value("abc-def").for(:permalink) }
24
+ it { should validate_presence_of(:permalink) }
25
+ it { should validate_uniqueness_of(:permalink) }
26
+
27
+ it { should validate_presence_of(:author) }
28
+ end
29
+
30
+ describe ".recently_published" do
31
+ before do
32
+ base = Time.now
33
+ @posts = (0...3).map do |i|
34
+ Post.make!(:published, :published_at => base + i.seconds)
35
+ end
36
+ @posts[1].published_at = base + 10.seconds
37
+ @posts[1].save!
38
+ @unpublished = Post.make!
39
+ end
40
+
41
+ it "does not include unpublished posts" do
42
+ Post.recently_published.should_not include(@unpublished)
43
+ end
44
+
45
+ it "returns in order of descending published_at" do
46
+ Post.recently_published.should == [@posts[1], @posts[2], @posts[0]]
47
+ end
48
+
49
+ it "excludes posts which have been explicitly unpublished" do
50
+ @posts[1].unpublish!
51
+ Post.recently_published.should == [@posts[2], @posts[0]]
52
+ end
53
+ end
54
+
55
+ describe "state machine" do
56
+ let(:post) { Post.make }
57
+
58
+ it "begins as unpublished" do
59
+ post.should be_unpublished
60
+ end
61
+
62
+ it "can be published and unpublished" do
63
+ post.publish!
64
+ post.should be_published
65
+
66
+ lambda do
67
+ post.publish!
68
+ end.should raise_error(StateMachine::InvalidTransition)
69
+
70
+ post.unpublish!
71
+ post.should be_unpublished
72
+
73
+ lambda do
74
+ post.unpublish!
75
+ end.should raise_error(StateMachine::InvalidTransition)
76
+ end
77
+ end
78
+
79
+ describe "published_at" do
80
+ it "is set automatically when post is first published" do
81
+ first_publication = Time.utc(1980, 1, 1)
82
+ second_publication = Time.utc(1980, 1, 2)
83
+
84
+ post = Post.make
85
+ post.published_at.should be_nil
86
+
87
+ Time.stub(:now) { first_publication }
88
+ post.publish
89
+ post.published_at.should == first_publication
90
+
91
+ Time.stub(:now) { second_publication }
92
+ post.publish
93
+ post.published_at.should == first_publication
94
+ end
95
+ end
96
+
97
+ describe "#author_byline" do
98
+ it "is set from the #author method before save" do
99
+ author = User.make(:email => 'jane@example.com')
100
+ Post.make!(:author => author).author_byline.
101
+ should == 'jane'
102
+ end
103
+ end
104
+
105
+ describe ".author_byline" do
106
+ it "returns .byline if present" do
107
+ author = mock('author', :byline => 'Jane Smith')
108
+ Post.author_byline(author).should == 'Jane Smith'
109
+ end
110
+
111
+ it "returns .email without domain if present" do
112
+ author = mock('author', :email => 'jane@example.com')
113
+ Post.author_byline(author).should == 'jane'
114
+ end
115
+
116
+ it "returns 'unknown' otherwise" do
117
+ author = mock('author')
118
+ Post.author_byline(author).should == 'unknown'
119
+ end
120
+ end
121
+ end