From d9d5ff4d069bf87afa73d8b5476e0a5769897ec8 Mon Sep 17 00:00:00 2001
From: lukemelia
Date: Thu, 1 Feb 2007 05:32:05 +0000
Subject: [PATCH] The projects controller gets more RESTy. It now supports XML,
RSS, ATOM, HTML and plain text views of the projects list.
Changes include:
* Add assert_xml_select method for testing RSS and ATOM results (Thanks, Jamis! http://weblog.jamisbuck.org/2007/1/4/assert_xml_select)
* Add resource_feeder plugin for generating RSS and ATOM feeds
* Update the URL on the Feeds page to use /projects.rss or /projects.txt instead of FeedController link
* Add created_at and updated_at timestamps to project table to support ATOM feeds
* Added new filter to login_system "login_or_feed_token_required" to allow RSS, ATOM or text requests with token-based authentication
Notes:
* This will break previous project listing feed subscriptions.
* RSS, ATOM & text feeds are available via session or HTTP_BASIC authentication, or by passing the user's token on the url; HTML and XML results are only available via session or HTTP_BASIC authentication
git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@415 a4c988fc-2ded-0310-b66e-134b36920a42
---
tracks/app/controllers/application.rb | 22 +++-
tracks/app/controllers/feed_controller.rb | 8 --
tracks/app/controllers/projects_controller.rb | 29 ++++-
tracks/app/controllers/users_controller.rb | 6 +-
tracks/app/helpers/application_helper.rb | 14 +--
tracks/app/helpers/feed_helper.rb | 14 ---
tracks/app/helpers/feedlist_helper.rb | 14 +++
tracks/app/models/project.rb | 21 ++++
tracks/app/models/user.rb | 4 +-
tracks/app/views/feed/projects_rss.rxml | 21 ----
tracks/app/views/feed/projects_text.rhtml | 2 -
tracks/app/views/feedlist/index.rhtml | 4 +-
tracks/app/views/projects/index_text.rhtml | 7 ++
tracks/app/views/todos/_item.rhtml | 2 +-
tracks/config/routes.rb | 1 -
.../db/migrate/026_add_project_timestamps.rb | 12 ++
tracks/db/schema.rb | 12 +-
tracks/lib/login_system.rb | 31 +++++-
tracks/test/fixtures/projects.yml | 13 +++
.../functional/feedlist_controller_test.rb | 30 +++++
.../functional/projects_controller_test.rb | 103 +++++++++++++++++-
tracks/test/integration/feed_smoke_test.rb | 6 +-
.../test/integration/project_xml_api_test.rb | 12 +-
tracks/test/integration/users_xml_api_test.rb | 2 -
tracks/test/test_helper.rb | 10 ++
tracks/test/unit/project_test.rb | 4 +
tracks/vendor/plugins/resource_feeder/README | 7 ++
.../vendor/plugins/resource_feeder/Rakefile | 22 ++++
tracks/vendor/plugins/resource_feeder/init.rb | 2 +
.../resource_feeder/lib/resource_feeder.rb | 2 +
.../lib/resource_feeder/atom.rb | 78 +++++++++++++
.../lib/resource_feeder/rss.rb | 79 ++++++++++++++
.../resource_feeder/test/atom_feed_test.rb | 85 +++++++++++++++
.../resource_feeder/test/rss_feed_test.rb | 86 +++++++++++++++
.../resource_feeder/test/test_helper.rb | 64 +++++++++++
35 files changed, 735 insertions(+), 94 deletions(-)
delete mode 100644 tracks/app/views/feed/projects_rss.rxml
delete mode 100644 tracks/app/views/feed/projects_text.rhtml
create mode 100644 tracks/app/views/projects/index_text.rhtml
create mode 100644 tracks/db/migrate/026_add_project_timestamps.rb
create mode 100644 tracks/test/functional/feedlist_controller_test.rb
create mode 100644 tracks/vendor/plugins/resource_feeder/README
create mode 100644 tracks/vendor/plugins/resource_feeder/Rakefile
create mode 100644 tracks/vendor/plugins/resource_feeder/init.rb
create mode 100644 tracks/vendor/plugins/resource_feeder/lib/resource_feeder.rb
create mode 100644 tracks/vendor/plugins/resource_feeder/lib/resource_feeder/atom.rb
create mode 100644 tracks/vendor/plugins/resource_feeder/lib/resource_feeder/rss.rb
create mode 100644 tracks/vendor/plugins/resource_feeder/test/atom_feed_test.rb
create mode 100644 tracks/vendor/plugins/resource_feeder/test/rss_feed_test.rb
create mode 100644 tracks/vendor/plugins/resource_feeder/test/test_helper.rb
diff --git a/tracks/app/controllers/application.rb b/tracks/app/controllers/application.rb
index 6477d384..a6635bdb 100644
--- a/tracks/app/controllers/application.rb
+++ b/tracks/app/controllers/application.rb
@@ -62,6 +62,26 @@ class ApplicationController < ActionController::Base
end
end
+ # Returns a count of next actions in the given context or project
+ # The result is count and a string descriptor, correctly pluralised if there are no
+ # actions or multiple actions
+ #
+ def count_undone_todos(todos_parent, string="actions")
+ if (todos_parent.is_a?(Project) && todos_parent.hidden?)
+ count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
+ else
+ count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
+ end
+ count = 0 if count == nil
+ #count = todos_parent.todos.select{|t| !t.done }.size
+ if count == 1
+ word = string.singularize
+ else
+ word = string.pluralize
+ end
+ return count.to_s + " " + word
+ end
+
protected
def admin_login_required
@@ -101,7 +121,7 @@ class ApplicationController < ActionController::Base
parents.each do |parent|
eval("@#{parent}_project_hidden_todo_counts = Todo.count(:conditions => ['user_id = ? and state = ?', @user.id, 'project_hidden'], :group => :#{parent}_id)")
end
- end
+ end
# Set the contents of the flash message from a controller
# Usage: notify :warning, "This is the message"
diff --git a/tracks/app/controllers/feed_controller.rb b/tracks/app/controllers/feed_controller.rb
index 0c81c432..33f6f9f4 100644
--- a/tracks/app/controllers/feed_controller.rb
+++ b/tracks/app/controllers/feed_controller.rb
@@ -45,14 +45,6 @@ class FeedController < ApplicationController
end
headers["Content-Type"] = "text/calendar"
end
-
- def list_projects_only
- init_not_done_counts('project')
- init_project_hidden_todo_counts
- @projects = @user.projects
- @description = "Lists all the projects for #{@user.login}."
- render :action => 'projects_' + params['feedtype']
- end
def list_contexts_only
init_not_done_counts('context')
diff --git a/tracks/app/controllers/projects_controller.rb b/tracks/app/controllers/projects_controller.rb
index b3ed580a..e5395018 100644
--- a/tracks/app/controllers/projects_controller.rb
+++ b/tracks/app/controllers/projects_controller.rb
@@ -1,14 +1,31 @@
class ProjectsController < ApplicationController
- helper :todos, :notes
+ helper :application, :todos, :notes
before_filter :init, :except => [:create, :destroy, :order]
+ skip_before_filter :login_required, :only => [:index]
+ prepend_before_filter :login_or_feed_token_required, :only => [:index]
+ session :off, :only => :index, :if => Proc.new { |req| ['rss','atom','txt'].include?(req.parameters[:format]) }
def index
- init_project_hidden_todo_counts
- @page_title = "TRACKS::List Projects"
- respond_to do |wants|
- wants.html
- wants.xml { render :xml => @projects.to_xml( :except => :user_id ) }
+ respond_to do |format|
+ format.html do
+ init_project_hidden_todo_counts
+ @page_title = "TRACKS::List Projects"
+ render
+ end
+ format.xml { render :xml => @projects.to_xml( :except => :user_id ) }
+ format.rss do
+ render_rss_feed_for @projects, :feed => Project.feed_options(@user),
+ :item => { :description => lambda { |p| p.summary(count_undone_todos(p)) } }
+ end
+ format.atom do
+ render_atom_feed_for @projects, :feed => Project.feed_options(@user),
+ :item => { :description => lambda { |p| p.summary(count_undone_todos(p)) },
+ :author => lambda { |p| nil } }
+ end
+ format.text do
+ render :action => 'index_text', :layout => false, :content_type => Mime::TEXT
+ end
end
end
diff --git a/tracks/app/controllers/users_controller.rb b/tracks/app/controllers/users_controller.rb
index 0a099172..e02d265b 100644
--- a/tracks/app/controllers/users_controller.rb
+++ b/tracks/app/controllers/users_controller.rb
@@ -12,6 +12,7 @@ class UsersController < ApplicationController
# GET /users
# GET /users.xml
def index
+ @users = User.find(:all, :order => 'login')
respond_to do |format|
format.html do
@page_title = "TRACKS::Manage Users"
@@ -21,10 +22,7 @@ class UsersController < ApplicationController
# we store the URL so that we get returned here when signup is successful
store_location
end
- format.xml do
- @users = User.find(:all)
- render :xml => @users.to_xml(:except => [ :password ])
- end
+ format.xml { render :xml => @users.to_xml(:except => [ :password ]) }
end
end
diff --git a/tracks/app/helpers/application_helper.rb b/tracks/app/helpers/application_helper.rb
index db40ffed..e69b8c27 100644
--- a/tracks/app/helpers/application_helper.rb
+++ b/tracks/app/helpers/application_helper.rb
@@ -113,19 +113,7 @@ module ApplicationHelper
# actions or multiple actions
#
def count_undone_todos(todos_parent, string="actions")
- if (todos_parent.is_a?(Project) && todos_parent.hidden?)
- count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
- else
- count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
- end
- count = 0 if count == nil
- #count = todos_parent.todos.select{|t| !t.done }.size
- if count == 1
- word = string.singularize
- else
- word = string.pluralize
- end
- return count.to_s + " " + word
+ @controller.count_undone_todos(todos_parent, string)
end
def link_to_context(context, descriptor = sanitize(context.name))
diff --git a/tracks/app/helpers/feed_helper.rb b/tracks/app/helpers/feed_helper.rb
index a2683a0a..c38bc212 100644
--- a/tracks/app/helpers/feed_helper.rb
+++ b/tracks/app/helpers/feed_helper.rb
@@ -31,20 +31,6 @@ module FeedHelper
return result_string
end
- def build_projects_text_page(projects)
- result_string = ""
- projects.each do |p|
- result_string << "\n" + p.name.upcase + "\n"
-
- result_string << p.description + "\n" if p.description_present?
- result_string << "#{count_undone_todos(p)}. Project is #{p.state}.\n"
- result_string << "#{p.linkurl}\n" if p.linkurl_present?
- result_string << "\n"
- end
-
- return result_string
- end
-
def build_contexts_text_page(contexts)
result_string = ""
contexts.each do |c|
diff --git a/tracks/app/helpers/feedlist_helper.rb b/tracks/app/helpers/feedlist_helper.rb
index 7ef3735b..ebbfd6b4 100644
--- a/tracks/app/helpers/feedlist_helper.rb
+++ b/tracks/app/helpers/feedlist_helper.rb
@@ -6,12 +6,26 @@ module FeedlistHelper
linkoptions.merge!(options)
link_to(image_tag, linkoptions, :title => "RSS feed")
end
+
+ def rss_formatted_link(options = {})
+ image_tag = image_tag("feed-icon.png", :size => "16X16", :border => 0, :class => "rss-icon")
+ linkoptions = { :token => @user.word, :format => 'rss' }
+ linkoptions.merge!(options)
+ link_to(image_tag, linkoptions, :title => "RSS feed")
+ end
def text_feed_link(options = {})
linkoptions = {:controller => 'feed', :action => 'text', :login => "#{@user.login}", :token => "#{@user.word}"}
linkoptions.merge!(options)
link_to('TXT', linkoptions, :title => "Plain text feed" )
end
+
+ def text_formatted_link(options = {})
+ linkoptions = { :token => @user.word, :format => 'txt' }
+ linkoptions.merge!(options)
+ link_to('TXT', linkoptions, :title => "Plain text feed" )
+ end
+
def ical_feed_link(options = {})
linkoptions = {:controller => 'feed', :action => 'ical', :login => "#{@user.login}", :token => "#{@user.word}"}
diff --git a/tracks/app/models/project.rb b/tracks/app/models/project.rb
index 20d9eb3e..2eb7b250 100644
--- a/tracks/app/models/project.rb
+++ b/tracks/app/models/project.rb
@@ -37,6 +37,13 @@ class Project < ActiveRecord::Base
NullProject.new
end
+ def self.feed_options(user)
+ {
+ :title => 'Tracks Projects',
+ :description => "Lists all the projects for #{user.display_name}."
+ }
+ end
+
def to_param
url_friendly_name
end
@@ -48,6 +55,20 @@ class Project < ActiveRecord::Base
def linkurl_present?
attribute_present?("linkurl")
end
+
+ def title
+ name
+ end
+
+ def summary(undone_todo_count)
+ project_description = ''
+ project_description += sanitize(markdown( description )) if description_present?
+ project_description += "#{undone_todo_count}. "
+ project_description += "Project is #{state}. "
+ project_description += "#{linkurl}" if linkurl_present?
+ project_description += "
"
+ project_description
+ end
def hide_todos
todos.each do |t|
diff --git a/tracks/app/models/user.rb b/tracks/app/models/user.rb
index 29a2f337..d740e155 100644
--- a/tracks/app/models/user.rb
+++ b/tracks/app/models/user.rb
@@ -8,12 +8,12 @@ class User < ActiveRecord::Base
:order => 'position ASC',
:dependent => :delete_all
has_many :todos,
- :order => 'completed_at DESC, created_at DESC',
+ :order => 'completed_at DESC, todos.created_at DESC',
:dependent => :delete_all
has_many :deferred_todos,
:class_name => 'Todo',
:conditions => [ 'state = ?', 'deferred' ],
- :order => 'show_from ASC, created_at DESC' do
+ :order => 'show_from ASC, todos.created_at DESC' do
def find_and_activate_ready
find(:all, :conditions => ['show_from <= ?', Time.now.utc.to_date ]).collect { |t| t.activate_and_save! }
end
diff --git a/tracks/app/views/feed/projects_rss.rxml b/tracks/app/views/feed/projects_rss.rxml
deleted file mode 100644
index 98b24906..00000000
--- a/tracks/app/views/feed/projects_rss.rxml
+++ /dev/null
@@ -1,21 +0,0 @@
-@headers["Content-Type"] = "text/xml; charset=utf-8"
-xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
- xml.channel do
- xml.title(@title)
- xml.link(projects_url)
- xml.description(@description)
- @projects.each do |p|
- xml.item do
- xml.title(p.name)
- xml.link(project_url(p))
- project_description = ''
- project_description += sanitize(markdown( p.description )) if p.description_present?
- project_description += "#{count_undone_todos(p)}. "
- project_description += "Project is #{p.state}. "
- project_description += "#{p.linkurl}" if p.linkurl_present?
- project_description += "
"
- xml.description(project_description)
- end
- end
- end
-end
\ No newline at end of file
diff --git a/tracks/app/views/feed/projects_text.rhtml b/tracks/app/views/feed/projects_text.rhtml
deleted file mode 100644
index cd17083e..00000000
--- a/tracks/app/views/feed/projects_text.rhtml
+++ /dev/null
@@ -1,2 +0,0 @@
-<% @headers["Content-Type"] = "text/plain; charset=utf-8" -%>
-<%= build_projects_text_page( @projects ) -%>
diff --git a/tracks/app/views/feedlist/index.rhtml b/tracks/app/views/feedlist/index.rhtml
index 4a1b6f0e..37563476 100644
--- a/tracks/app/views/feedlist/index.rhtml
+++ b/tracks/app/views/feedlist/index.rhtml
@@ -45,8 +45,8 @@
All Contexts
- <%= rss_feed_link({ :action => 'list_projects_only', :feedtype => 'rss' }) %>
- <%= text_feed_link({ :action => 'list_projects_only', :feedtype => 'text' }) %>
+ <%= rss_formatted_link({:controller => 'projects', :action => 'index'}) %>
+ <%= text_formatted_link({:controller => 'projects', :action => 'index'}) %>
All Projects
Feeds for uncompleted actions in a specific context:
diff --git a/tracks/app/views/projects/index_text.rhtml b/tracks/app/views/projects/index_text.rhtml
new file mode 100644
index 00000000..e6caee2e
--- /dev/null
+++ b/tracks/app/views/projects/index_text.rhtml
@@ -0,0 +1,7 @@
+<% @projects.each do |p| -%>
+
+<%= p.name.upcase %>
+<%= p.description + "\n" if p.description_present? -%>
+<%= count_undone_todos(p)%>. Project is <%= p.state %>.
+<%= p.linkurl + "\n" if p.linkurl_present? -%>
+<% end -%>
\ No newline at end of file
diff --git a/tracks/app/views/todos/_item.rhtml b/tracks/app/views/todos/_item.rhtml
index c8c9dac1..72eaa1b4 100644
--- a/tracks/app/views/todos/_item.rhtml
+++ b/tracks/app/views/todos/_item.rhtml
@@ -35,7 +35,7 @@
<% end -%>
<% if item.completed? -%>
- (<%= item.context.name %><%= ", " + item.project.name if item.project_id %>)
+ (<%= item.context.name %><%= ", " + item.project.name unless item.project.nil? %>)
<% else -%>
<% if (parent_container_type == "project" || parent_container_type == "tickler") -%>
<%= item_link_to_context( item ) %>
diff --git a/tracks/config/routes.rb b/tracks/config/routes.rb
index 9a4b3ae1..12908d19 100644
--- a/tracks/config/routes.rb
+++ b/tracks/config/routes.rb
@@ -36,7 +36,6 @@ ActionController::Routing::Routes.draw do |map|
# Projects Routes
map.resources :projects, :collection => {:order => :post}
map.connect 'project/:project/feed/:action/:login/:token', :controller => 'feed'
- map.connect 'projects/feed/:feedtype/:login/:token', :controller => 'feed', :action => 'list_projects_only'
# Notes Routes
map.resources :notes
diff --git a/tracks/db/migrate/026_add_project_timestamps.rb b/tracks/db/migrate/026_add_project_timestamps.rb
new file mode 100644
index 00000000..ebbfb0a9
--- /dev/null
+++ b/tracks/db/migrate/026_add_project_timestamps.rb
@@ -0,0 +1,12 @@
+class AddProjectTimestamps < ActiveRecord::Migration
+ def self.up
+ add_column :projects, :created_at, :timestamp
+ add_column :projects, :updated_at, :timestamp
+ end
+
+
+ def self.down
+ remove_column :projects, :created_at
+ remove_column :projects, :updated_at
+ end
+end
diff --git a/tracks/db/schema.rb b/tracks/db/schema.rb
index f3fde4cc..388a2b41 100644
--- a/tracks/db/schema.rb
+++ b/tracks/db/schema.rb
@@ -2,7 +2,7 @@
# migrations feature of ActiveRecord to incrementally modify your database, and
# then regenerate this schema definition.
-ActiveRecord::Schema.define(:version => 25) do
+ActiveRecord::Schema.define(:version => 26) do
create_table "contexts", :force => true do |t|
t.column "name", :string, :default => "", :null => false
@@ -60,11 +60,13 @@ ActiveRecord::Schema.define(:version => 25) do
add_index "preferences", ["user_id"], :name => "index_preferences_on_user_id"
create_table "projects", :force => true do |t|
- t.column "name", :string, :default => "", :null => false
- t.column "position", :integer, :default => 0, :null => false
- t.column "user_id", :integer, :default => 0, :null => false
+ t.column "name", :string, :default => "", :null => false
+ t.column "position", :integer, :default => 0, :null => false
+ t.column "user_id", :integer, :default => 0, :null => false
t.column "description", :text
- t.column "state", :string, :limit => 20, :default => "active", :null => false
+ t.column "state", :string, :limit => 20, :default => "active", :null => false
+ t.column "created_at", :datetime
+ t.column "updated_at", :datetime
end
add_index "projects", ["user_id"], :name => "index_projects_on_user_id"
diff --git a/tracks/lib/login_system.rb b/tracks/lib/login_system.rb
index cd2edd52..d3a94d49 100644
--- a/tracks/lib/login_system.rb
+++ b/tracks/lib/login_system.rb
@@ -30,6 +30,16 @@ module LoginSystem
def protect?(action)
true
end
+
+ def login_or_feed_token_required
+ if ['rss', 'atom', 'txt'].include?(params[:format])
+ if user = User.find_by_word(params[:token])
+ set_current_user(user)
+ return true
+ end
+ end
+ login_required
+ end
# login_required filter. add
#
@@ -53,7 +63,7 @@ module LoginSystem
http_user, http_pass = get_basic_auth_data
if user = User.authenticate(http_user, http_pass)
session['user_id'] = user.id
- get_current_user
+ set_current_user(user)
return true
end
@@ -75,7 +85,7 @@ module LoginSystem
http_user, http_pass = get_basic_auth_data
if user = User.authenticate(http_user, http_pass)
session['user_id'] = user.id
- get_current_user
+ set_current_user(user)
return true
end
@@ -89,6 +99,12 @@ module LoginSystem
@prefs = @user.prefs unless @user.nil?
@user
end
+
+ def set_current_user(user)
+ @user = user
+ @prefs = @user.prefs unless @user.nil?
+ @user
+ end
# overwrite if you want to have special behavior in case the user is not authorized
# to access the current operation.
@@ -96,10 +112,13 @@ module LoginSystem
# example use :
# a popup window might just close itself for instance
def access_denied
- respond_to do |wants|
- wants.html { redirect_to :controller=>"login", :action =>"login" }
- wants.js { render :partial => 'login/redirect_to_login' }
- wants.xml { basic_auth_denied }
+ respond_to do |format|
+ format.html { redirect_to :controller=>"login", :action =>"login" }
+ format.js { render :partial => 'login/redirect_to_login' }
+ format.xml { basic_auth_denied }
+ format.rss { basic_auth_denied }
+ format.atom { basic_auth_denied }
+ format.text { basic_auth_denied }
end
end
diff --git a/tracks/test/fixtures/projects.yml b/tracks/test/fixtures/projects.yml
index 5276c6df..36404845 100644
--- a/tracks/test/fixtures/projects.yml
+++ b/tracks/test/fixtures/projects.yml
@@ -1,4 +1,11 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+<%
+
+def today
+ Time.now.utc.to_s(:db)
+end
+
+%>
timemachine:
id: 1
@@ -7,6 +14,8 @@ timemachine:
position: 1
state: 'active'
user_id: 1
+ created_at: <%= today %>
+ updated_at: <%= today %>
moremoney:
id: 2
@@ -15,6 +24,8 @@ moremoney:
position: 2
state: 'active'
user_id: 1
+ created_at: <%= today %>
+ updated_at: <%= today %>
gardenclean:
id: 3
@@ -23,3 +34,5 @@ gardenclean:
position: 3
state: 'active'
user_id: 1
+ created_at: <%= today %>
+ updated_at: <%= today %>
diff --git a/tracks/test/functional/feedlist_controller_test.rb b/tracks/test/functional/feedlist_controller_test.rb
new file mode 100644
index 00000000..bcf346d8
--- /dev/null
+++ b/tracks/test/functional/feedlist_controller_test.rb
@@ -0,0 +1,30 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'feedlist_controller'
+
+# Re-raise errors caught by the controller.
+class FeedlistController; def rescue_action(e) raise e end; end
+
+class FeedlistControllerTest < Test::Unit::TestCase
+ fixtures :users, :preferences, :projects, :contexts, :todos, :notes
+
+ def setup
+ assert_equal "test", ENV['RAILS_ENV']
+ assert_equal "change-me", Tracks::Config.salt
+ @controller = FeedlistController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_get_index_when_not_logged_in
+ get :index
+ assert_redirected_to :controller => 'login', :action => 'login'
+ end
+
+ def test_get_index_by_logged_in_user
+ @request.session['user_id'] = users(:other_user).id
+ get :index
+ assert_response :success
+ assert_equal "TRACKS::Feeds", assigns['page_title']
+ end
+
+end
diff --git a/tracks/test/functional/projects_controller_test.rb b/tracks/test/functional/projects_controller_test.rb
index b07b98b0..4fbec5ae 100644
--- a/tracks/test/functional/projects_controller_test.rb
+++ b/tracks/test/functional/projects_controller_test.rb
@@ -6,7 +6,7 @@ require 'projects_controller'
class ProjectsController; def rescue_action(e) raise e end; end
class ProjectsControllerTest < TodoContainerControllerTestBase
- fixtures :users, :todos, :preferences, :projects
+ fixtures :users, :todos, :preferences, :projects, :contexts
def setup
perform_setup(Project, ProjectsController)
@@ -78,5 +78,106 @@ class ProjectsControllerTest < TodoContainerControllerTestBase
assert p.reload().active?
end
+ def test_rss_feed_content
+ @request.session['user_id'] = users(:admin_user).id
+ get :index, { :format => "rss" }
+ assert_equal 'application/rss+xml; charset=utf-8', @response.headers["Content-Type"]
+ #puts @response.body
+
+ assert_xml_select 'rss[version="2.0"]' do
+ assert_xml_select 'channel' do
+ assert_xml_select '>title', 'Tracks Projects'
+ assert_xml_select '>description', "Lists all the projects for #{users(:admin_user).display_name}."
+ assert_xml_select 'language', 'en-us'
+ assert_xml_select 'ttl', '40'
+ end
+ assert_xml_select 'item', 3 do
+ assert_xml_select 'title', /.+/
+ assert_xml_select 'description', /<p>\d+ actions. Project is (active|hidden|completed). <\/p>/
+ %w(guid link).each do |node|
+ assert_xml_select node, /http:\/\/test.host\/projects\/.+/
+ end
+ assert_xml_select 'pubDate', projects(:timemachine).created_at.to_s(:rfc822)
+ end
+ end
+ end
+
+ def test_rss_feed_not_accessible_to_anonymous_user_without_token
+ @request.session['user_id'] = nil
+ get :index, { :format => "rss" }
+ assert_response 401
+ end
+
+ def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token
+ @request.session['user_id'] = nil
+ get :index, { :format => "rss", :token => 'foo' }
+ assert_response 401
+ end
+
+ def test_rss_feed_accessible_to_anonymous_user_with_valid_token
+ @request.session['user_id'] = nil
+ get :index, { :format => "rss", :token => users(:admin_user).word }
+ assert_response :ok
+ end
+
+ def test_atom_feed_content
+ @request.session['user_id'] = users(:admin_user).id
+ get :index, { :format => "atom" }
+ assert_equal 'application/atom+xml; charset=utf-8', @response.headers["Content-Type"]
+ #puts @response.body
+
+ assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
+ assert_xml_select '>title', 'Tracks Projects'
+ assert_xml_select '>subtitle', "Lists all the projects for #{users(:admin_user).display_name}."
+ assert_xml_select 'entry', 3 do
+ assert_xml_select 'title', /.+/
+ assert_xml_select 'content[type="html"]', /<p>\d+ actions. Project is (active|hidden|completed). <\/p>/
+ assert_xml_select 'published', projects(:timemachine).created_at.to_s(:rfc822)
+ end
+ end
+ end
+
+ def test_atom_feed_not_accessible_to_anonymous_user_without_token
+ @request.session['user_id'] = nil
+ get :index, { :format => "atom" }
+ assert_response 401
+ end
+
+ def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token
+ @request.session['user_id'] = nil
+ get :index, { :format => "atom", :token => 'foo' }
+ assert_response 401
+ end
+
+ def test_atom_feed_accessible_to_anonymous_user_with_valid_token
+ @request.session['user_id'] = nil
+ get :index, { :format => "atom", :token => users(:admin_user).word }
+ assert_response :ok
+ end
+
+ def test_text_feed_content
+ @request.session['user_id'] = users(:admin_user).id
+ get :index, { :format => "txt" }
+ assert_equal 'text/plain; charset=utf-8', @response.headers["Content-Type"]
+ #puts @response.body
+ end
+
+ def test_text_feed_not_accessible_to_anonymous_user_without_token
+ @request.session['user_id'] = nil
+ get :index, { :format => "txt" }
+ assert_response 401
+ end
+
+ def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token
+ @request.session['user_id'] = nil
+ get :index, { :format => "txt", :token => 'foo' }
+ assert_response 401
+ end
+
+ def test_text_feed_accessible_to_anonymous_user_with_valid_token
+ @request.session['user_id'] = nil
+ get :index, { :format => "txt", :token => users(:admin_user).word }
+ assert_response :ok
+ end
end
diff --git a/tracks/test/integration/feed_smoke_test.rb b/tracks/test/integration/feed_smoke_test.rb
index 27b6755a..438cd7f7 100644
--- a/tracks/test/integration/feed_smoke_test.rb
+++ b/tracks/test/integration/feed_smoke_test.rb
@@ -80,17 +80,17 @@ class FeedSmokeTest < ActionController::IntegrationTest
end
def test_all_projects_rss
- assert_success "/projects/feed/rss/admin/#{ users(:admin_user).word }"
+ assert_success "/projects.rss?token=#{ users(:admin_user).word }"
end
def test_all_projects_txt
- assert_success "/projects/feed/text/admin/#{ users(:admin_user).word }"
+ assert_success "/projects.txt?token=#{ users(:admin_user).word }"
end
def test_all_projects_txt_with_hidden_project
p = projects(:timemachine)
p.hide!
- assert_success "/projects/feed/text/admin/#{ users(:admin_user).word }"
+ assert_success "/projects.txt?token=#{ users(:admin_user).word }"
end
private
diff --git a/tracks/test/integration/project_xml_api_test.rb b/tracks/test/integration/project_xml_api_test.rb
index 5cb17c6f..7f37daf6 100644
--- a/tracks/test/integration/project_xml_api_test.rb
+++ b/tracks/test/integration/project_xml_api_test.rb
@@ -4,7 +4,7 @@ require 'projects_controller'
# Re-raise errors caught by the controller.
class ProjectsController; def rescue_action(e) raise e end; end
-class ProjectsControllerXmlApiTest < ActionController::IntegrationTest
+class ProjectXmlApiTest < ActionController::IntegrationTest
fixtures :users, :projects
@@project_name = "My New Project"
@@ -51,7 +51,15 @@ class ProjectsControllerXmlApiTest < ActionController::IntegrationTest
def test_creates_new_project
initial_count = Project.count
authenticated_post_xml_to_project_create
- assert_response_and_body_matches 200, %r|^<\?xml version="1\.0" encoding="UTF-8"\?>\n\n \n [0-9]+\n #{@@project_name}\n 1\n active\n$|
+ assert_response :success
+ assert_xml_select 'project' do
+ assert_xml_select "description"
+ assert_xml_select 'id[type="integer"]', /[0-9]+/
+ assert_xml_select 'name', @@project_name
+ assert_xml_select 'position[type="integer"]', 1
+ assert_xml_select 'state', 'active'
+ end
+ #assert_response_and_body_matches 200, %r|^<\?xml version="1\.0" encoding="UTF-8"\?>\n\n \n [0-9]+\n #{@@project_name}\n 1\n active\n$|
assert_equal initial_count + 1, Project.count
project1 = Project.find_by_name(@@project_name)
assert_not_nil project1, "expected project '#{@@project_name}' to be created"
diff --git a/tracks/test/integration/users_xml_api_test.rb b/tracks/test/integration/users_xml_api_test.rb
index 634c3739..31bd5c63 100644
--- a/tracks/test/integration/users_xml_api_test.rb
+++ b/tracks/test/integration/users_xml_api_test.rb
@@ -76,7 +76,6 @@ class UsersXmlApiTest < ActionController::IntegrationTest
def test_get_users_as_xml
get '/users.xml', {}, basic_auth_headers()
- #puts @response.body
assert_response :success
assert_tag :tag => "users",
:children => { :count => 3, :only => { :tag => "user" } }
@@ -85,7 +84,6 @@ class UsersXmlApiTest < ActionController::IntegrationTest
def test_get_user_as_xml
get "/users/#{users(:other_user).login}.xml", {}, basic_auth_headers()
- puts @response.body
assert_response :success
assert_tag :tag => "user"
assert_no_tag :tag => "password"
diff --git a/tracks/test/test_helper.rb b/tracks/test/test_helper.rb
index f2d21701..9d203af9 100644
--- a/tracks/test/test_helper.rb
+++ b/tracks/test/test_helper.rb
@@ -30,6 +30,16 @@ class Test::Unit::TestCase
end
return string
end
+
+ def xml_document
+ @xml_document ||= HTML::Document.new(@response.body, false, true)
+ end
+
+ def assert_xml_select(*args)
+ @html_document = xml_document
+ assert_select(*args)
+ end
+
end
class ActionController::IntegrationTest
diff --git a/tracks/test/unit/project_test.rb b/tracks/test/unit/project_test.rb
index 3963d85a..7b18729f 100644
--- a/tracks/test/unit/project_test.rb
+++ b/tracks/test/unit/project_test.rb
@@ -143,4 +143,8 @@ class ProjectTest < Test::Unit::TestCase
assert_equal 'Build_a_working_time_machine', @timemachine.to_param
end
+ def test_title_reader_returns_name
+ assert_equal @timemachine.name, @timemachine.title
+ end
+
end
diff --git a/tracks/vendor/plugins/resource_feeder/README b/tracks/vendor/plugins/resource_feeder/README
new file mode 100644
index 00000000..5502be25
--- /dev/null
+++ b/tracks/vendor/plugins/resource_feeder/README
@@ -0,0 +1,7 @@
+ResourceFeeder
+==============
+
+Simple feeds for resources
+
+NOTE: This plugin depends on the latest version of simply_helpful, available here:
+http://dev.rubyonrails.org/svn/rails/plugins/simply_helpful/
diff --git a/tracks/vendor/plugins/resource_feeder/Rakefile b/tracks/vendor/plugins/resource_feeder/Rakefile
new file mode 100644
index 00000000..51fce7b3
--- /dev/null
+++ b/tracks/vendor/plugins/resource_feeder/Rakefile
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the resource_feed plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the resource_feed plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'ResourceFeed'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
diff --git a/tracks/vendor/plugins/resource_feeder/init.rb b/tracks/vendor/plugins/resource_feeder/init.rb
new file mode 100644
index 00000000..7b55d76f
--- /dev/null
+++ b/tracks/vendor/plugins/resource_feeder/init.rb
@@ -0,0 +1,2 @@
+require 'resource_feeder'
+ActionController::Base.send(:include, ResourceFeeder::Rss, ResourceFeeder::Atom)
\ No newline at end of file
diff --git a/tracks/vendor/plugins/resource_feeder/lib/resource_feeder.rb b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder.rb
new file mode 100644
index 00000000..b5003419
--- /dev/null
+++ b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder.rb
@@ -0,0 +1,2 @@
+require 'resource_feeder/rss'
+require 'resource_feeder/atom'
diff --git a/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/atom.rb b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/atom.rb
new file mode 100644
index 00000000..d3b5a63c
--- /dev/null
+++ b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/atom.rb
@@ -0,0 +1,78 @@
+module ResourceFeeder
+ module Atom
+ extend self
+
+ def render_atom_feed_for(resources, options = {})
+ render :text => atom_feed_for(resources, options), :content_type => Mime::ATOM
+ end
+
+ def atom_feed_for(resources, options = {})
+ xml = Builder::XmlMarkup.new(:indent => 2)
+
+ options[:feed] ||= {}
+ options[:item] ||= {}
+ options[:url_writer] ||= self
+
+ if options[:class] || resources.first
+ klass = options[:class] || resources.first.class
+ new_record = klass.new
+ else
+ options[:feed] = { :title => "Empty", :link => "http://example.com" }
+ end
+
+ options[:feed][:title] ||= klass.name.pluralize
+ options[:feed][:id] ||= "tag:#{request.host_with_port}:#{klass.name.pluralize}"
+ options[:feed][:link] ||= SimplyHelpful::RecordIdentifier.polymorphic_url(new_record, options[:url_writer])
+
+ options[:item][:title] ||= [ :title, :subject, :headline, :name ]
+ options[:item][:description] ||= [ :description, :body, :content ]
+ options[:item][:pub_date] ||= [ :updated_at, :updated_on, :created_at, :created_on ]
+ options[:item][:author] ||= [ :author, :creator ]
+
+ resource_link = lambda { |r| SimplyHelpful::RecordIdentifier.polymorphic_url(r, options[:url_writer]) }
+
+ xml.instruct!
+ xml.feed "xml:lang" => "en-US", "xmlns" => 'http://www.w3.org/2005/Atom' do
+ xml.title(options[:feed][:title])
+ xml.id(options[:feed][:id])
+ xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:feed][:link])
+ xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:feed][:self]) if options[:feed][:self]
+ xml.subtitle(options[:feed][:description]) if options[:feed][:description]
+
+ for resource in resources
+ published_at = call_or_read(options[:item][:pub_date], resource)
+
+ xml.entry do
+ xml.title(call_or_read(options[:item][:title], resource))
+ xml.content(call_or_read(options[:item][:description], resource), :type => 'html')
+ xml.id("tag:#{request.host_with_port},#{published_at.xmlschema}:#{call_or_read(options[:item][:guid] || options[:item][:link] || resource_link, resource)}")
+ xml.published(published_at.xmlschema)
+ xml.updated((resource.respond_to?(:updated_at) ? call_or_read(options[:item][:pub_date] || :updated_at, resource) : published_at).xmlschema)
+ xml.link(:rel => 'alternate', :type => 'text/html', :href => call_or_read(options[:item][:link] || options[:item][:guid] || resource_link, resource))
+
+ if author = call_or_read(options[:item][:author], resource)
+ xml.author do
+ xml.name()
+ end
+ end
+ end
+ end
+ end
+ end
+
+ private
+ def call_or_read(procedure_or_attributes, resource)
+ case procedure_or_attributes
+ when Array
+ attributes = procedure_or_attributes
+ resource.send(attributes.select { |a| resource.respond_to?(a) }.first)
+ when Symbol
+ attribute = procedure_or_attributes
+ resource.send(attribute)
+ when Proc
+ procedure = procedure_or_attributes
+ procedure.call(resource)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/rss.rb b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/rss.rb
new file mode 100644
index 00000000..b66ec4a8
--- /dev/null
+++ b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/rss.rb
@@ -0,0 +1,79 @@
+module ResourceFeeder
+ module Rss
+ extend self
+
+ def render_rss_feed_for(resources, options = {})
+ render :text => rss_feed_for(resources, options), :content_type => Mime::RSS
+ end
+
+ def rss_feed_for(resources, options = {})
+ xml = Builder::XmlMarkup.new(:indent => 2)
+
+ options[:feed] ||= {}
+ options[:item] ||= {}
+ options[:url_writer] ||= self
+
+ if options[:class] || resources.first
+ klass = options[:class] || resources.first.class
+ new_record = klass.new
+ else
+ options[:feed] = { :title => "Empty", :link => "http://example.com" }
+ end
+ use_content_encoded = options[:item].has_key?(:content_encoded)
+
+ options[:feed][:title] ||= klass.name.pluralize
+ options[:feed][:link] ||= SimplyHelpful::RecordIdentifier.polymorphic_url(new_record, options[:url_writer])
+ options[:feed][:language] ||= "en-us"
+ options[:feed][:ttl] ||= "40"
+
+ options[:item][:title] ||= [ :title, :subject, :headline, :name ]
+ options[:item][:description] ||= [ :description, :body, :content ]
+ options[:item][:pub_date] ||= [ :updated_at, :updated_on, :created_at, :created_on ]
+
+ resource_link = lambda { |r| SimplyHelpful::RecordIdentifier.polymorphic_url(r, options[:url_writer]) }
+
+ rss_root_attributes = { :version => 2.0 }
+ rss_root_attributes.merge!("xmlns:content" => "http://purl.org/rss/1.0/modules/content/") if use_content_encoded
+
+ xml.instruct!
+
+ xml.rss(rss_root_attributes) do
+ xml.channel do
+ xml.title(options[:feed][:title])
+ xml.link(options[:feed][:link])
+ xml.description(options[:feed][:description]) if options[:feed][:description]
+ xml.language(options[:feed][:language])
+ xml.ttl(options[:feed][:ttl])
+
+ for resource in resources
+ xml.item do
+ xml.title(call_or_read(options[:item][:title], resource))
+ xml.description(call_or_read(options[:item][:description], resource))
+ if use_content_encoded then
+ xml.content(:encoded) { xml.cdata!(call_or_read(options[:item][:content_encoded], resource)) }
+ end
+ xml.pubDate(call_or_read(options[:item][:pub_date], resource).to_s(:rfc822))
+ xml.guid(call_or_read(options[:item][:guid] || options[:item][:link] || resource_link, resource))
+ xml.link(call_or_read(options[:item][:link] || options[:item][:guid] || resource_link, resource))
+ end
+ end
+ end
+ end
+ end
+
+ private
+ def call_or_read(procedure_or_attributes, resource)
+ case procedure_or_attributes
+ when Array
+ attributes = procedure_or_attributes
+ resource.send(attributes.select { |a| resource.respond_to?(a) }.first)
+ when Symbol
+ attribute = procedure_or_attributes
+ resource.send(attribute)
+ when Proc
+ procedure = procedure_or_attributes
+ procedure.call(resource)
+ end
+ end
+ end
+end
diff --git a/tracks/vendor/plugins/resource_feeder/test/atom_feed_test.rb b/tracks/vendor/plugins/resource_feeder/test/atom_feed_test.rb
new file mode 100644
index 00000000..3112da47
--- /dev/null
+++ b/tracks/vendor/plugins/resource_feeder/test/atom_feed_test.rb
@@ -0,0 +1,85 @@
+require File.dirname(__FILE__) + '/test_helper'
+class AtomFeedTest < Test::Unit::TestCase
+ attr_reader :request
+
+ def setup
+ @request = OpenStruct.new
+ @request.host_with_port = 'example.com'
+ @records = Array.new(5).fill(Post.new)
+ @records.each &:save
+ end
+
+ def test_default_atom_feed
+ atom_feed_for @records
+
+ assert_select 'feed' do
+ assert_select '>title', 'Posts'
+ assert_select '>id', "tag:#{request.host_with_port}:Posts"
+ assert_select '>link' do
+ assert_select "[rel='alternate']"
+ assert_select "[type='text/html']"
+ assert_select "[href='http://example.com/posts']"
+ end
+ assert_select 'entry', 5 do
+ assert_select 'title', :text => 'feed title (title)'
+ assert_select "content[type='html']", '<p>feed description (description)</p>'
+ assert_select 'id', "tag:#{request.host_with_port},#{@records.first.created_at.xmlschema}:#{'http://example.com/posts/1'}"
+ assert_select 'published', @records.first.created_at.xmlschema
+ assert_select 'updated', @records.first.created_at.xmlschema
+ assert_select 'link' do
+ assert_select "[rel='alternate']"
+ assert_select "[type='text/html']"
+ assert_select "[href='http://example.com/posts/1']"
+ end
+ end
+ end
+ end
+
+ def test_should_allow_custom_feed_options
+ atom_feed_for @records, :feed => { :title => 'Custom Posts', :link => '/posts', :description => 'stuff', :self => '/posts.atom' }
+
+ assert_select 'feed>title', 'Custom Posts'
+ assert_select "feed>link[href='/posts']"
+ assert_select 'feed>subtitle', 'stuff'
+ assert_select 'feed>link' do
+ assert_select "[rel='self']"
+ assert_select "[type='application/atom+xml']"
+ assert_select "[href='/posts.atom']"
+ end
+ end
+
+ def test_should_allow_custom_item_attributes
+ atom_feed_for @records, :item => { :title => :name, :description => :body, :pub_date => :create_date, :link => :id }
+
+ assert_select 'entry', 5 do
+ assert_select 'title', :text => 'feed title (name)'
+ assert_select "content[type='html']", '<p>feed description (body)</p>'
+ assert_select 'published', (@records.first.created_at - 5.minutes).xmlschema
+ assert_select 'updated', (@records.first.created_at - 5.minutes).xmlschema
+ assert_select 'id', "tag:#{request.host_with_port},#{(@records.first.created_at - 5.minutes).xmlschema}:1"
+ assert_select 'link' do
+ assert_select "[rel='alternate']"
+ assert_select "[type='text/html']"
+ assert_select "[href='1']"
+ end
+ end
+ end
+
+ def test_should_allow_custom_item_attribute_blocks
+ atom_feed_for @records, :item => { :title => lambda { |r| r.name }, :description => lambda { |r| r.body }, :pub_date => lambda { |r| r.create_date },
+ :link => lambda { |r| "/#{r.created_at.to_i}" }, :guid => lambda { |r| r.created_at.to_i } }
+
+ assert_select 'entry', 5 do
+ assert_select 'title', :text => 'feed title (name)'
+ assert_select "content[type='html']", '<p>feed description (body)</p>'
+ assert_select 'published', (@records.first.created_at - 5.minutes).xmlschema
+ assert_select 'updated', (@records.first.created_at - 5.minutes).xmlschema
+ assert_select 'id', /:\d+$/
+ assert_select 'link' do
+ assert_select "[rel='alternate']"
+ assert_select "[type='text/html']"
+ assert_select "[href=?]", /^\/\d+$/
+ end
+ end
+ end
+end
diff --git a/tracks/vendor/plugins/resource_feeder/test/rss_feed_test.rb b/tracks/vendor/plugins/resource_feeder/test/rss_feed_test.rb
new file mode 100644
index 00000000..90525baf
--- /dev/null
+++ b/tracks/vendor/plugins/resource_feeder/test/rss_feed_test.rb
@@ -0,0 +1,86 @@
+require File.dirname(__FILE__) + '/test_helper'
+class RssFeedTest < Test::Unit::TestCase
+ def setup
+ @records = Array.new(5).fill(Post.new)
+ @records.each &:save
+ end
+
+ def test_default_rss_feed
+ rss_feed_for @records
+
+ assert_select 'rss[version="2.0"]' do
+ assert_select 'channel' do
+ assert_select '>title', 'Posts'
+ assert_select '>link', 'http://example.com/posts'
+ assert_select 'language', 'en-us'
+ assert_select 'ttl', '40'
+ end
+ assert_select 'item', 5 do
+ assert_select 'title', :text => 'feed title (title)'
+ assert_select 'description', '<p>feed description (description)</p>'
+ %w(guid link).each do |node|
+ assert_select node, 'http://example.com/posts/1'
+ end
+ assert_select 'pubDate', @records.first.created_at.to_s(:rfc822)
+ end
+ end
+ end
+
+ def test_should_allow_custom_feed_options
+ rss_feed_for @records, :feed => { :title => 'Custom Posts', :link => '/posts', :description => 'stuff', :language => 'en-gb', :ttl => '80' }
+
+ assert_select 'channel>title', 'Custom Posts'
+ assert_select 'channel>link', '/posts'
+ assert_select 'channel>description', 'stuff'
+ assert_select 'channel>language', 'en-gb'
+ assert_select 'channel>ttl', '80'
+ end
+
+ def test_should_allow_custom_item_attributes
+ rss_feed_for @records, :item => { :title => :name, :description => :body, :pub_date => :create_date, :link => :id }
+
+ assert_select 'item', 5 do
+ assert_select 'title', :text => 'feed title (name)'
+ assert_select 'description', '<p>feed description (body)</p>'
+ assert_select 'pubDate', (@records.first.created_at - 5.minutes).to_s(:rfc822)
+ assert_select 'link', '1'
+ assert_select 'guid', '1'
+ end
+ end
+
+ def test_should_allow_custom_item_attribute_blocks
+ rss_feed_for @records, :item => { :title => lambda { |r| r.name }, :description => lambda { |r| r.body }, :pub_date => lambda { |r| r.create_date },
+ :link => lambda { |r| "/#{r.created_at.to_i}" }, :guid => lambda { |r| r.created_at.to_i } }
+
+ assert_select 'item', 5 do
+ assert_select 'title', :text => 'feed title (name)'
+ assert_select 'description', '<p>feed description (body)</p>'
+ assert_select 'pubDate', (@records.first.created_at - 5.minutes).to_s(:rfc822)
+ end
+ end
+
+ # note that assert_select isnt easily able to get elements that have xml namespaces (as it thinks they are
+ # invalid html psuedo children), so we do some manual testing with the response body
+ def test_should_allow_content_encoded_for_items
+ rss_feed_for @records, :item => { :content_encoded => :full_html_body }
+
+ html_content = "Here is some full content, with out any excerpts"
+ assert_equal 5, @response.body.scan("").size
+ assert_select 'item', 5 do
+ assert_select 'description + *', " { :content_encoded => :full_html_body }
+ assert_equal %[\n],
+ @response.body.grep(/\n],
+ @response.body.grep(/feed description (#{attr_name})
"
+ end
+ end
+
+ def full_html_body
+ "Here is some full content, with out any excerpts"
+ end
+
+ def create_date
+ @created_at - 5.minutes
+ end
+end
+
+class Test::Unit::TestCase
+ include ResourceFeeder::Rss, ResourceFeeder::Atom
+
+ def render_feed(xml)
+ @response = OpenStruct.new
+ @response.headers = {'Content-Type' => 'text/xml'}
+ @response.body = xml
+ end
+
+ def rss_feed_for_with_ostruct(resources, options = {})
+ render_feed rss_feed_for_without_ostruct(resources, options)
+ end
+
+ def atom_feed_for_with_ostruct(resources, options = {})
+ render_feed atom_feed_for_without_ostruct(resources, options)
+ end
+
+ alias_method_chain :rss_feed_for, :ostruct
+ alias_method_chain :atom_feed_for, :ostruct
+
+ def html_document
+ @html_document ||= HTML::Document.new(@response.body, false, true)
+ end
+
+ def posts_url
+ "http://example.com/posts"
+ end
+
+ def post_url(post)
+ "http://example.com/posts/#{post.id}"
+ end
+end