diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6
index 745a172d2..1d6a08359 100644
--- a/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6
@@ -1,4 +1,4 @@
-import { outputExportResult } from 'admin/lib/export-result';
+import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6
index b06c74d04..0427fe0fb 100644
--- a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6
@@ -1,4 +1,4 @@
-import { outputExportResult } from 'admin/lib/export-result';
+import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6
index 49637494c..153a38337 100644
--- a/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6
@@ -1,4 +1,4 @@
-import { outputExportResult } from 'admin/lib/export-result';
+import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
diff --git a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6
index 9d0437493..c4ecd3d14 100644
--- a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6
@@ -6,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
-import { outputExportResult } from 'admin/lib/export-result';
+import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
diff --git a/app/assets/javascripts/admin/routes/admin-users-list.js.es6 b/app/assets/javascripts/admin/routes/admin-users-list.js.es6
index 1932799c6..1fcc89095 100644
--- a/app/assets/javascripts/admin/routes/admin-users-list.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-users-list.js.es6
@@ -1,4 +1,4 @@
-import { outputExportResult } from 'admin/lib/export-result';
+import { outputExportResult } from 'discourse/lib/export-result';
export default Discourse.Route.extend({
diff --git a/app/assets/javascripts/discourse/controllers/user.js.es6 b/app/assets/javascripts/discourse/controllers/user.js.es6
index 5fb5b5bb8..bf2b7d21b 100644
--- a/app/assets/javascripts/discourse/controllers/user.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user.js.es6
@@ -1,5 +1,6 @@
import ObjectController from 'discourse/controllers/object';
import CanCheckEmails from 'discourse/mixins/can-check-emails';
+import { outputExportResult } from 'discourse/lib/export-result';
export default ObjectController.extend(CanCheckEmails, {
indexStream: false,
@@ -51,6 +52,10 @@ export default ObjectController.extend(CanCheckEmails, {
Discourse.AdminUser.find(this.get('username').toLowerCase()).then(function(user){
user.destroy({deletePosts: true});
});
+ },
+
+ exportUserArchive: function() {
+ Discourse.ExportCsv.exportUserArchive().then(outputExportResult);
}
}
});
diff --git a/app/assets/javascripts/admin/lib/export-result.js.es6 b/app/assets/javascripts/discourse/lib/export-result.js.es6
similarity index 100%
rename from app/assets/javascripts/admin/lib/export-result.js.es6
rename to app/assets/javascripts/discourse/lib/export-result.js.es6
diff --git a/app/assets/javascripts/admin/models/export_csv.js b/app/assets/javascripts/discourse/models/export_csv.js
similarity index 50%
rename from app/assets/javascripts/admin/models/export_csv.js
rename to app/assets/javascripts/discourse/models/export_csv.js
index 082ddeb00..a2f559968 100644
--- a/app/assets/javascripts/admin/models/export_csv.js
+++ b/app/assets/javascripts/discourse/models/export_csv.js
@@ -9,13 +9,22 @@
Discourse.ExportCsv = Discourse.Model.extend({});
Discourse.ExportCsv.reopenClass({
+ /**
+ Exports user archive
+
+ @method export_user_archive
+ **/
+ exportUserArchive: function() {
+ return Discourse.ajax("/export_csv/export_entity.json", {data: {entity_type: 'user', entity: 'user_archive'}});
+ },
+
/**
Exports user list
@method export_user_list
**/
exportUserList: function() {
- return Discourse.ajax("/admin/export_csv/export_entity.json", {data: {entity: 'user'}});
+ return Discourse.ajax("/export_csv/export_entity.json", {data: {entity_type: 'admin', entity: 'user'}});
},
/**
@@ -24,7 +33,7 @@ Discourse.ExportCsv.reopenClass({
@method export_staff_action_logs
**/
exportStaffActionLogs: function() {
- return Discourse.ajax("/admin/export_csv/export_entity.json", {data: {entity: 'staff_action'}});
+ return Discourse.ajax("/export_csv/export_entity.json", {data: {entity_type: 'admin', entity: 'staff_action'}});
},
/**
@@ -33,7 +42,7 @@ Discourse.ExportCsv.reopenClass({
@method export_screened_email_list
**/
exportScreenedEmailList: function() {
- return Discourse.ajax("/admin/export_csv/export_entity.json", {data: {entity: 'screened_email'}});
+ return Discourse.ajax("/export_csv/export_entity.json", {data: {entity_type: 'admin', entity: 'screened_email'}});
},
/**
@@ -42,7 +51,7 @@ Discourse.ExportCsv.reopenClass({
@method export_screened_ip_list
**/
exportScreenedIpList: function() {
- return Discourse.ajax("/admin/export_csv/export_entity.json", {data: {entity: 'screened_ip'}});
+ return Discourse.ajax("/export_csv/export_entity.json", {data: {entity_type: 'admin', entity: 'screened_ip'}});
},
/**
@@ -51,6 +60,6 @@ Discourse.ExportCsv.reopenClass({
@method export_screened_url_list
**/
exportScreenedUrlList: function() {
- return Discourse.ajax("/admin/export_csv/export_entity.json", {data: {entity: 'screened_url'}});
+ return Discourse.ajax("/export_csv/export_entity.json", {data: {entity_type: 'admin', entity: 'screened_url'}});
}
});
diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs
index 1d5731b3d..524a0232f 100644
--- a/app/assets/javascripts/discourse/templates/user/user.hbs
+++ b/app/assets/javascripts/discourse/templates/user/user.hbs
@@ -186,6 +186,12 @@
{{/if}}
+
+ {{#if viewingSelf}}
+
+
+
+ {{/if}}
diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js
index 920c91263..63f9997d0 100644
--- a/app/assets/javascripts/main_include.js
+++ b/app/assets/javascripts/main_include.js
@@ -56,6 +56,7 @@
//= require ./discourse/helpers/cold-age-class
//= require ./discourse/helpers/loading-spinner
//= require ./discourse/helpers/category-link
+//= require ./discourse/lib/export-result
//= require ./discourse/dialects/dialect
//= require ./discourse/lib/emoji/emoji
@@ -69,4 +70,3 @@
//= require_tree ./discourse/templates
//= require_tree ./discourse/routes
//= require_tree ./discourse/initializers
-
diff --git a/app/assets/javascripts/main_include_admin.js b/app/assets/javascripts/main_include_admin.js
index 00050ed3d..dd77d21b0 100644
--- a/app/assets/javascripts/main_include_admin.js
+++ b/app/assets/javascripts/main_include_admin.js
@@ -1,8 +1,8 @@
//= require list-view
-//= require admin/lib/export-result
//= require admin/models/user-field
//= require admin/controllers/admin-email-skipped
//= require admin/controllers/change-site-customization-details
+//= require discourse/lib/export-result
//= require_tree ./admin
//= require resumable.js
diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss
index 41e6923ab..1eea26e44 100644
--- a/app/assets/stylesheets/desktop/user.scss
+++ b/app/assets/stylesheets/desktop/user.scss
@@ -139,6 +139,11 @@
}
}
+ .user-archive {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ }
+
.user-right.groups {
margin-top: 0;
}
diff --git a/app/controllers/admin/export_csv_controller.rb b/app/controllers/admin/export_csv_controller.rb
deleted file mode 100644
index 0ab4aefe0..000000000
--- a/app/controllers/admin/export_csv_controller.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-class Admin::ExportCsvController < Admin::AdminController
-
- skip_before_filter :check_xhr, only: [:show]
-
- def export_entity
- params.require(:entity)
- # export csv file in a background thread
- Jobs.enqueue(:export_csv_file, entity: params[:entity], user_id: current_user.id)
- render json: success_json
- end
-
- # download
- def show
- filename = params.fetch(:id)
- if export_csv_path = ExportCsv.get_download_path(filename)
- send_file export_csv_path
- else
- render nothing: true, status: 404
- end
- end
-
-end
diff --git a/app/controllers/export_csv_controller.rb b/app/controllers/export_csv_controller.rb
new file mode 100644
index 000000000..b10840540
--- /dev/null
+++ b/app/controllers/export_csv_controller.rb
@@ -0,0 +1,32 @@
+class ExportCsvController < ApplicationController
+
+ skip_before_filter :check_xhr, only: [:show]
+
+ def export_entity
+ params.require(:entity)
+ params.require(:entity_type)
+ if params[:entity_type] == "admin"
+ guardian.ensure_can_export_admin_entity!(current_user)
+ end
+
+ Jobs.enqueue(:export_csv_file, entity: params[:entity], user_id: current_user.id)
+ render json: success_json
+ end
+
+ # download
+ def show
+ params.require(:id)
+ filename = params.fetch(:id)
+ export_id = filename.split('_')[1].split('.')[0]
+ export_initiated_by_user_id = 0
+ export_initiated_by_user_id = CsvExportLog.where(id: export_id)[0].user_id unless CsvExportLog.where(id: export_id).empty?
+ export_csv_path = CsvExportLog.get_download_path(filename)
+
+ if export_csv_path && export_initiated_by_user_id == current_user.id
+ send_file export_csv_path
+ else
+ render nothing: true, status: 404
+ end
+ end
+
+end
diff --git a/app/jobs/regular/export_csv_file.rb b/app/jobs/regular/export_csv_file.rb
index f8c2a2f79..c0daa07bd 100644
--- a/app/jobs/regular/export_csv_file.rb
+++ b/app/jobs/regular/export_csv_file.rb
@@ -5,6 +5,7 @@ module Jobs
class ExportCsvFile < Jobs::Base
HEADER_ATTRS_FOR = {}
+ HEADER_ATTRS_FOR['user_archive'] = ['raw','like_count','reply_count','created_at']
HEADER_ATTRS_FOR['user'] = ['id','name','username','email','title','created_at','trust_level','active','admin','moderator','ip_address']
HEADER_ATTRS_FOR['user_stats'] = ['topics_entered','posts_read_count','time_read','topic_count','post_count','likes_given','likes_received']
HEADER_ATTRS_FOR['user_sso'] = ['external_id','external_email', 'external_username', 'external_name', 'external_avatar_url']
@@ -18,10 +19,16 @@ module Jobs
def initialize
@file_name = ""
+ @entity_type = "admin"
end
def execute(args)
entity = args[:entity]
+
+ if entity == "user_archive"
+ @entity_type = "user"
+ end
+
@current_user = User.find_by(id: args[:user_id])
export_method = "#{entity}_export".to_sym
@@ -41,6 +48,13 @@ module Jobs
notify_user
end
+ def user_archive_export
+ user_archive_data = Post.where(user_id: @current_user.id).select(HEADER_ATTRS_FOR['user_archive']).with_deleted.to_a
+ user_archive_data.map do |user_archive|
+ get_user_archive_fields(user_archive)
+ end
+ end
+
def user_export
query = ::AdminUserIndexQuery.new
user_data = query.find_users_query.to_a
@@ -113,6 +127,16 @@ module Jobs
return group_names
end
+ def get_user_archive_fields(user_archive)
+ user_archive_array = []
+
+ HEADER_ATTRS_FOR['user_archive'].each do |attr|
+ user_archive_array.push(user_archive.attributes[attr])
+ end
+
+ user_archive_array
+ end
+
def get_user_fields(user)
user_array = []
@@ -215,15 +239,17 @@ module Jobs
def set_file_path
- @file_name = "export_#{SecureRandom.hex(4)}.csv"
+ @file = CsvExportLog.create(export_type: @entity_type, user_id: @current_user.id)
+ @file_name = "export_#{@file.id}.csv"
+
# ensure directory exists
- dir = File.dirname("#{ExportCsv.base_directory}/#{@file_name}")
+ dir = File.dirname("#{CsvExportLog.base_directory}/#{@file_name}")
FileUtils.mkdir_p(dir) unless Dir.exists?(dir)
end
def write_csv_file(data, header)
# write to CSV file
- CSV.open(File.expand_path("#{ExportCsv.base_directory}/#{@file_name}", __FILE__), "w") do |csv|
+ CSV.open(File.expand_path("#{CsvExportLog.base_directory}/#{@file_name}", __FILE__), "w") do |csv|
csv << header
data.each do |value|
csv << value
@@ -233,8 +259,8 @@ module Jobs
def notify_user
if @current_user
- if @file_name != "" && File.exists?("#{ExportCsv.base_directory}/#{@file_name}")
- SystemMessage.create_from_system_user(@current_user, :csv_export_succeeded, download_link: "#{Discourse.base_url}/admin/export_csv/#{@file_name}", file_name: @file_name)
+ if @file_name != "" && File.exists?("#{CsvExportLog.base_directory}/#{@file_name}")
+ SystemMessage.create_from_system_user(@current_user, :csv_export_succeeded, download_link: "#{Discourse.base_url}/export_csv/#{@file_name}", file_name: @file_name)
else
SystemMessage.create_from_system_user(@current_user, :csv_export_failed)
end
diff --git a/app/jobs/scheduled/clean_up_exports.rb b/app/jobs/scheduled/clean_up_exports.rb
index 27469d10a..e61a96bd0 100644
--- a/app/jobs/scheduled/clean_up_exports.rb
+++ b/app/jobs/scheduled/clean_up_exports.rb
@@ -3,7 +3,7 @@ module Jobs
every 2.day
def execute(args)
- ExportCsv.remove_old_exports # delete exported CSV files older than 2 days
+ CsvExportLog.remove_old_exports # delete exported CSV files older than 2 days
end
end
end
diff --git a/app/models/api_key.rb b/app/models/api_key.rb
index 514c1ba4b..9145ce437 100644
--- a/app/models/api_key.rb
+++ b/app/models/api_key.rb
@@ -31,6 +31,7 @@ end
# created_at :datetime not null
# updated_at :datetime not null
# allowed_ips :inet is an Array
+# hidden :boolean default(FALSE), not null
#
# Indexes
#
diff --git a/app/models/csv_export_log.rb b/app/models/csv_export_log.rb
new file mode 100644
index 000000000..c16dfe022
--- /dev/null
+++ b/app/models/csv_export_log.rb
@@ -0,0 +1,40 @@
+class CsvExportLog < ActiveRecord::Base
+
+ def self.get_download_path(filename)
+ path = File.join(CsvExportLog.base_directory, filename)
+ if File.exists?(path)
+ return path
+ else
+ nil
+ end
+ end
+
+ def self.remove_old_exports
+ expired_exports = CsvExportLog.where('created_at < ?', 2.days.ago).to_a
+ expired_exports.map do |expired_export|
+ file_name = "export_#{expired_export.id}.csv"
+ file_path = "#{CsvExportLog.base_directory}/#{file_name}"
+
+ if File.exist?(file_path)
+ File.delete(file_path)
+ end
+ CsvExportLog.find(expired_export.id).destroy
+ end
+ end
+
+ def self.base_directory
+ File.join(Rails.root, "public", "uploads", "csv_exports", RailsMultisite::ConnectionManagement.current_db)
+ end
+
+end
+
+# == Schema Information
+#
+# Table name: csv_export_logs
+#
+# id :integer not null, primary key
+# export_type :string(255) not null
+# user_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+#
diff --git a/app/models/export_csv.rb b/app/models/export_csv.rb
deleted file mode 100644
index fcbae3705..000000000
--- a/app/models/export_csv.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-class ExportCsv
-
- def self.get_download_path(filename)
- path = File.join(ExportCsv.base_directory, filename)
- if File.exists?(path)
- return path
- else
- nil
- end
- end
-
- def self.remove_old_exports
- if Dir.exists?(ExportCsv.base_directory)
- Dir.foreach(ExportCsv.base_directory) do |file|
- path = File.join(ExportCsv.base_directory, file)
- next if File.directory? path
-
- if (File.mtime(path) < 2.days.ago)
- File.delete(path)
- end
- end
- end
- end
-
- def self.base_directory
- File.join(Rails.root, "public", "uploads", "csv_exports", RailsMultisite::ConnectionManagement.current_db)
- end
-
-end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 33c117ab6..df0a64b66 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -292,7 +292,7 @@ en:
profile: "Profile"
mute: "Mute"
edit: "Edit Preferences"
- download_archive: "download archive of my posts"
+ download_archive: "Download archive of my posts"
new_private_message: "New Private Message"
private_message: "Private Message"
private_messages: "Messages"
diff --git a/config/routes.rb b/config/routes.rb
index 308bbf3cf..81a8d29ef 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -164,15 +164,6 @@ Discourse::Application.routes.draw do
end
end
- resources :export_csv, constraints: AdminConstraint.new do
- collection do
- get "export_entity" => "export_csv#export_entity"
- end
- member do
- get "" => "export_csv#show", constraints: { id: /[^\/]+/ }
- end
- end
-
resources :badges, constraints: AdminConstraint.new do
collection do
get "types" => "badges#badge_types"
@@ -441,6 +432,15 @@ Discourse::Application.routes.draw do
get "invites/redeem/:token" => "invites#redeem_disposable_invite"
delete "invites" => "invites#destroy"
+ resources :export_csv do
+ collection do
+ get "export_entity" => "export_csv#export_entity"
+ end
+ member do
+ get "" => "export_csv#show", constraints: { id: /[^\/]+/ }
+ end
+ end
+
get "onebox" => "onebox#show"
get "error" => "forums#error"
diff --git a/db/migrate/20141223145058_create_csv_export_logs.rb b/db/migrate/20141223145058_create_csv_export_logs.rb
new file mode 100644
index 000000000..83291db28
--- /dev/null
+++ b/db/migrate/20141223145058_create_csv_export_logs.rb
@@ -0,0 +1,9 @@
+class CreateCsvExportLogs < ActiveRecord::Migration
+ def change
+ create_table :csv_export_logs do |t|
+ t.string :export_type, null: false
+ t.integer :user_id, null: false
+ t.timestamps
+ end
+ end
+end
diff --git a/lib/guardian.rb b/lib/guardian.rb
index f0998e057..97643ff3a 100644
--- a/lib/guardian.rb
+++ b/lib/guardian.rb
@@ -249,6 +249,10 @@ class Guardian
@can_see_emails
end
+ def can_export_admin_entity?(user)
+ user.staff?
+ end
+
private
def is_my_own?(obj)
diff --git a/spec/controllers/admin/export_csv_controller_spec.rb b/spec/controllers/admin/export_csv_controller_spec.rb
deleted file mode 100644
index 644deb6d9..000000000
--- a/spec/controllers/admin/export_csv_controller_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require "spec_helper"
-
-describe Admin::ExportCsvController do
-
- it "is a subclass of AdminController" do
- (Admin::ExportCsvController < Admin::AdminController).should == true
- end
-
- let(:export_filename) { "export_b6a2bc87.csv" }
-
- context "while logged in as an admin" do
-
- before { @admin = log_in(:admin) }
-
- describe ".download" do
-
- it "uses send_file to transmit the export file" do
- controller.stubs(:render)
- export = ExportCsv.new()
- ExportCsv.expects(:get_download_path).with(export_filename).returns(export)
- subject.expects(:send_file).with(export)
- get :show, id: export_filename
- end
-
- it "returns 404 when the export file does not exist" do
- ExportCsv.expects(:get_download_path).returns(nil)
- get :show, id: export_filename
- response.should be_not_found
- end
-
- end
-
- end
-
-end
diff --git a/spec/controllers/export_csv_controller_spec.rb b/spec/controllers/export_csv_controller_spec.rb
new file mode 100644
index 000000000..4de76e21c
--- /dev/null
+++ b/spec/controllers/export_csv_controller_spec.rb
@@ -0,0 +1,80 @@
+require "spec_helper"
+
+describe ExportCsvController do
+ let(:export_filename) { "export_999.csv" }
+
+
+ context "while logged in as normal user" do
+ before { @user = log_in(:user) }
+
+ describe ".export_entity" do
+ it "enqueues export job" do
+ Jobs.expects(:enqueue).with(:export_csv_file, has_entries(entity: "user_archive", user_id: @user.id))
+ xhr :post, :export_entity, entity: "user_archive", entity_type: "user"
+ response.should be_success
+ end
+
+ it "returns 404 when normal user tries to export admin entity" do
+ xhr :post, :export_entity, entity: "staff_action", entity_type: "admin"
+ response.should_not be_success
+ end
+ end
+
+ describe ".download" do
+ it "uses send_file to transmit the export file" do
+ file = CsvExportLog.create(export_type: "user", user_id: @user.id)
+ file_name = "export_#{file.id}.csv"
+ controller.stubs(:render)
+ export = CsvExportLog.new()
+ CsvExportLog.expects(:get_download_path).with(file_name).returns(export)
+ subject.expects(:send_file).with(export)
+ get :show, id: file_name
+ response.should be_success
+ end
+
+ it "returns 404 when the user tries to export another user's csv file" do
+ get :show, id: export_filename
+ response.should be_not_found
+ end
+
+ it "returns 404 when the export file does not exist" do
+ CsvExportLog.expects(:get_download_path).returns(nil)
+ get :show, id: export_filename
+ response.should be_not_found
+ end
+ end
+ end
+
+
+ context "while logged in as an admin" do
+ before { @admin = log_in(:admin) }
+
+ describe ".export_entity" do
+ it "enqueues export job" do
+ Jobs.expects(:enqueue).with(:export_csv_file, has_entries(entity: "staff_action", user_id: @admin.id))
+ xhr :post, :export_entity, entity: "staff_action", entity_type: "admin"
+ response.should be_success
+ end
+ end
+
+ describe ".download" do
+ it "uses send_file to transmit the export file" do
+ file = CsvExportLog.create(export_type: "admin", user_id: @admin.id)
+ file_name = "export_#{file.id}.csv"
+ controller.stubs(:render)
+ export = CsvExportLog.new()
+ CsvExportLog.expects(:get_download_path).with(file_name).returns(export)
+ subject.expects(:send_file).with(export)
+ get :show, id: file_name
+ response.should be_success
+ end
+
+ it "returns 404 when the export file does not exist" do
+ CsvExportLog.expects(:get_download_path).returns(nil)
+ get :show, id: export_filename
+ response.should be_not_found
+ end
+ end
+ end
+
+end