require File.expand_path('../../test_helper', __FILE__) describe "Back" do include RackTestMethods start do root = Dir.mktmpdir app_root = File.expand_path('../../fixtures/back', __FILE__) FileUtils.cp_r(app_root, root) root += "/back" FileUtils.mkdir_p(root / "cache") FileUtils.cp_r(File.join(File.dirname(__FILE__), "../fixtures/media"), root / "cache") Spontaneous::Permissions::UserLevel.reset! @level_file = root / "config/user_levels.yml" Spontaneous::Permissions::UserLevel.stubs(:level_file).returns(@level_file) let(:site_root) { root } Spontaneous::Permissions::User.delete # annoying to have to do this, but there you go user = Spontaneous::Permissions::User.create(:email => "root@example.com", :login => "root", :name => "root name", :password => "rootpass") user.update(:level => Spontaneous::Permissions[:editor]) user.save.reload key = user.generate_access_key("127.0.0.1") Spontaneous::Permissions::User.stubs(:[]).with(:login => 'root').returns(user) Spontaneous::Permissions::User.stubs(:[]).with(user.id).returns(user) Spontaneous::Permissions::AccessKey.stubs(:authenticate).with(key.key_id).returns(key) let(:user) { user } let(:key) { key } site = setup_site(root, true) site.config.tap do |c| c.reload_classes = false c.auto_login = 'root' c.default_charset = 'utf-8' c.background_mode = :immediate c.site_domain = 'example.org' c.site_id = 'example_org' end let(:site) { site } Content.delete Page.field :title class ::Project < Page; end class ::Image < Piece field :image, :image end class ::Job < Piece field :title field :image, :image field :client, :select, :options => [["a", "Value A"], ["b", "Value B"]] box :images do field :title field :image allow Image end end class ::LinkedJob < Piece alias_of proc { |owner, box| box.contents } end class ::HomePage < Page field :introduction, :richtext box :projects do allow Project end box :in_progress do allow Job allow Image end box :featured_jobs do allow LinkedJob end end class ::AdminAccess < Page field :title field :private, :user_level => :root end home = HomePage.new(:title => "Home") project1 = Project.new(:title => "Project 1", :slug => "project1") project2 = Project.new(:title => "Project 2", :slug => "project2") project3 = Project.new(:title => "Project 3", :slug => "project3") home.projects << project1 home.projects << project2 home.projects << project3 job1 = Job.new(:title => "Job 1", :image => "/i/job1.jpg") job2 = Job.new(:title => "Job 2", :image => "/i/job2.jpg") job3 = Job.new(:title => "Job 3", :image => "/i/job3.jpg") image1 = Image.new job1.images << image1 home.in_progress << job1 home.in_progress << job2 home.in_progress << job3 home.save let(:home_id) { home.id } let(:project1_id) { project1.id } let(:project2_id) { project2.id } let(:project3_id) { project3.id } let(:job1_id) { job1.id } let(:job2_id) { job2.id } let(:job3_id) { job3.id } let(:image1_id) { image1.id } end finish do [:Page, :Piece, :HomePage, :Job, :Project, :Image, :LinkedJob, :AdminAccess].each do |klass| Object.send(:remove_const, klass) rescue nil end Spontaneous::Permissions::User.delete Content.delete if defined?(Content) teardown_site(true) end def app @app ||= Spontaneous::Rack::Back.application(site) end let(:home) { Content[home_id] } let(:project1) { Content[project1_id] } let(:project2) { Content[project2_id] } let(:project3) { Content[project3_id] } let(:job1) { Content[job1_id] } let(:job2) { Content[job2_id] } let(:job3) { Content[job3_id] } let(:image1) { Content[image1_id] } before do @now = Time.now stub_time(@now) storage = site.default_storage site.stubs(:storage).with(anything).returns(storage) end # Used by the various auth_* methods def api_key key end it "retrieves /@spontaneous without a CSRF token" do get("/@spontaneous") assert last_response.ok?, "Index retrieval should succeed without CSRF tokens" assert_contains_csrf_token(key) end it "retrieves /@spontaneous/ without a CSRF token" do get("/@spontaneous/") assert last_response.ok?, "Index retrieval should succeed without CSRF tokens" assert_contains_csrf_token(key) end it "retrieves any page identified by an id without a CSRF token" do get("/@spontaneous/#{project1.id}/edit") assert last_response.ok?, "Index retrieval should succeed without CSRF tokens" assert_contains_csrf_token(key) end describe "/@spontaneous" do before do self.template_root = File.expand_path('../../fixtures/back/templates', __FILE__) @app_dir = File.expand_path("../../fixtures/application", __FILE__) assert File.exists?(@app_dir) Spontaneous.stubs(:application_dir).returns(@app_dir) end it "returns application page" do get '/@spontaneous/' assert last_response.ok?, "Should have returned 200 but got #{last_response.status}" last_response.body.must_match /
Updated intro
\n" end it "triggers replacement of default slug if title is first set" do project = Project.new home.projects << project home.save assert project.has_generated_slug? params = { "field[#{home.fields.title.schema_id.to_s}]" => "Updated title", } auth_put "/@spontaneous/content/#{project.id}", params project.reload project.slug.must_equal "updated-title" refute project.has_generated_slug? end it "updates box field values" do box = job1.images box.fields.title.to_s.wont_equal "Updated title" params = { "field[#{box.fields.title.schema_id.to_s}]" => "Updated title" } auth_put "/@spontaneous/content/#{job1.id}/#{box.schema_id.to_s}", params assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" job = Content[job1.id] job.images.title.value.must_equal "Updated title" end it "toggles visibility" do job1.reload.visible?.must_equal true auth_patch "/@spontaneous/content/#{job1.id}/toggle" assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" Spot::JSON.parse(last_response.body).must_equal({:id => job1.id, :hidden => true}) job1.reload.visible?.must_equal false auth_patch "/@spontaneous/content/#{job1.id}/toggle" assert last_response.ok?, "Expected status 200 but recieved #{last_response.status}" job1.reload.visible?.must_equal true Spot::JSON.parse(last_response.body).must_equal({:id => job1.id, :hidden => false}) end it "sets the position of pieces" do auth_patch "/@spontaneous/content/#{job2.id}/position/0" assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" home.reload home.in_progress.contents.first.id.must_equal job2.id page = Content[home.id] page.in_progress.contents.first.id.must_equal job2.id end it "records the currently logged in user" do page = home.in_progress.last auth_patch "/@spontaneous/content/#{page.id}/toggle" assert last_response.ok?, "Expected status 200 but received #{last_response.status}" page.reload page.pending_modifications(:visibility).first.user.must_equal user end it "allows addition of pages" do current_count = home.projects.length auth_post "/@spontaneous/content/#{home.id}/#{home.projects.schema_id.to_s}/#{Project.schema_id.to_s}" assert last_response.ok?, "Recieved #{last_response.status} not 200" home.reload home.projects.length.must_equal current_count+1 home.projects.first.must_be_instance_of(Project) end it "default to adding entries at the top" do current_count = home.in_progress.contents.length first_id = home.in_progress.contents.first.id home.in_progress.contents.first.class.name.wont_equal "Image" auth_post "/@spontaneous/content/#{home.id}/#{home.in_progress.schema_id.to_s}/#{Image.schema_id.to_s}" assert last_response.ok?, "Recieved #{last_response.status} not 200" last_response.content_type.must_equal "application/json;charset=utf-8" home.reload home.in_progress.contents.length.must_equal current_count+1 home.in_progress.contents.first.id.wont_equal first_id home.in_progress.contents.first.class.name.must_equal "Image" required_response = { :position => 0, :entry => home.in_progress.contents.first.export } Spot::JSON.parse(last_response.body).must_equal required_response end it "allows adding of entries at the bottom" do current_count = home.in_progress.contents.length last_id = home.in_progress.contents.last.id home.in_progress.contents.last.class.name.wont_equal "Image" auth_post "/@spontaneous/content/#{home.id}/#{home.in_progress.schema_id.to_s}/#{Image.schema_id.to_s}", :position => -1 assert last_response.ok?, "Recieved #{last_response.status} not 200" last_response.content_type.must_equal "application/json;charset=utf-8" home.reload home.in_progress.contents.length.must_equal current_count+1 home.in_progress.contents.last.id.wont_equal last_id home.in_progress.contents.last.class.name.must_equal "Image" required_response = { :position => -1, :entry => home.in_progress.contents.last.export } Spot::JSON.parse(last_response.body).must_equal required_response end it "creates entries with the owner set to the logged in user" do auth_post "/@spontaneous/content/#{home.id}/#{home.in_progress.schema_id.to_s}/#{Image.schema_id.to_s}", :position => 0 assert last_response.ok?, "Recieved #{last_response.status} not 200" home.reload home.in_progress.first.created_by_id.must_equal user.id home.in_progress.first.created_by.must_equal user end it "allows the deletion of items" do target = home.in_progress.first auth_delete "/@spontaneous/content/#{target.id}" assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" Content[target.id].must_be_nil end end describe "/file" do before do @src_file = Pathname.new(File.join(File.dirname(__FILE__), "../fixtures/images/rose.jpg")).realpath.to_s @upload_id = 9723 Spontaneous::Media.stubs(:upload_index).returns(23) @upload = ::Rack::Test::UploadedFile.new(@src_file, "image/jpeg") end it "replace values of fields immediately when required" do image1.image.processed_value.must_equal "" auth_put("@spontaneous/file/#{image1.id}", "file" => @upload, "field" => image1.image.schema_id.to_s ) assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" image = Content[image1.id] src = image.image.src src.must_match /^\/media(.+)\/rose\.jpg$/ Spot::JSON.parse(last_response.body).must_equal image.image.export assert File.exist?(S::Media.to_filepath(src)) get src assert last_response.ok? end it "replace values of box file fields" do job1.images.image.processed_value.must_equal "" auth_put("@spontaneous/file/#{job1.id}/#{job1.images.schema_id}", "file" => @upload, "field" => job1.images.image.schema_id.to_s) assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" job = Content[job1.id] src = job.images.image.src src.must_match /^\/media(.+)\/rose\.jpg$/ Spot::JSON.parse(last_response.body).must_equal job.images.image.export assert File.exist?(S::Media.to_filepath(src)) get src assert last_response.ok? end it "be able to wrap pieces around files using default addable class" do box = job1.images current_count = box.contents.length first_id = box.contents.first.id.to_s auth_post "/@spontaneous/file/#{job1.id}/#{box.schema_id.to_s}", "file" => @upload assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" box = job1.reload.images first = box.contents.first box.contents.length.must_equal current_count+1 first.image.src.must_match /^\/media(.+)\/#{File.basename(@src_file)}$/ required_response = { :position => 0, :entry => first.export } Spot::JSON.parse(last_response.body).must_equal required_response end end describe "/shard" do before do Spontaneous.stubs(:reload!) @image = File.expand_path("../../fixtures/sharding/rose.jpg", __FILE__) # read the digest dynamically in case I change that image @image_digest = S::Media.digest(@image) end it "has the right setting for shard_dir" do shard_path = File.join(site.root / 'cache/tmp') Spontaneous.shard_path.must_equal shard_path Spontaneous.shard_path("abcdef0123").must_equal shard_path/ "ab/cd/abcdef0123" end it "knows when it already has a shard" do hash = '4d68c8f13459c0edb40504de5003ec2a6b74e613' FileUtils.touch(Spontaneous.shard_path(hash)) FileUtils.expects(:touch).with(Spontaneous.shard_path(hash)) auth_get "/@spontaneous/shard/#{hash}" last_response.status.must_equal 200 end it "knows when it doesn't have a shard" do auth_get "/@spontaneous/shard/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" last_response.status.must_equal 404 end it "receives a shard and puts it in the right place" do auth_post "@spontaneous/shard/#{@image_digest}", "file" => ::Rack::Test::UploadedFile.new(@image, "image/jpeg") assert last_response.ok? auth_get "/@spontaneous/shard/#{@image_digest}" last_response.status.must_equal 200 end it "returns an error if the uploaded file has the wrong hash" do S::Media.expects(:digest).with(anything).returns("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") auth_post "@spontaneous/shard/#{@image_digest}", "file" => ::Rack::Test::UploadedFile.new(@image, "image/jpeg") last_response.status.must_equal 409 end it "reassembles multiple parts into a single file and attaches it to a content item" do parts = %w(xaa xab xac xad xae xaf xag) paths = parts.map { |part| File.expand_path("../../fixtures/sharding/#{part}", __FILE__) } hashes = paths.map { |path| S::Media.digest(path) } paths.each_with_index do |part, n| auth_post "/@spontaneous/shard/#{hashes[n]}", "file" => ::Rack::Test::UploadedFile.new(part, "application/octet-stream") end hashes.each do |hash| auth_get "/@spontaneous/shard/#{hash}" last_response.status.must_equal 200 end image1.image.processed_value.must_equal "" dataset = mock() ::Content.stubs(:for_update).returns(dataset) dataset.stubs(:get).with(image1.id.to_s).returns(image1) dataset.stubs(:get).with(image1.id).returns(image1) auth_put "/@spontaneous/shard/#{image1.id}", "filename" => "rose.jpg", "shards" => hashes, "field" => image1.image.schema_id.to_s assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" image = Content[image1.id] src = image.image.src src.must_match %r{^(.+)/rose\.jpg$} Spot::JSON.parse(last_response.body).must_equal image.image.export assert File.exist?(S::Media.to_filepath(src)) end it "wraps pieces around files using default addable class" do parts = %w(xaa xab xac xad xae xaf xag) paths = parts.map { |part| File.expand_path("../../fixtures/sharding/#{part}", __FILE__) } hashes = paths.map { |path| S::Media.digest(path) } paths.each_with_index do |part, n| auth_post "/@spontaneous/shard/#{hashes[n]}", "file" => ::Rack::Test::UploadedFile.new(part, "application/octet-stream") end hashes.each do |hash| auth_get "/@spontaneous/shard/#{hash}" last_response.status.must_equal 200 end box = job1.images current_count = box.contents.length first_id = box.contents.first.id.to_s auth_post "/@spontaneous/shard/#{job1.id}/#{box.schema_id.to_s}", "filename" => "rose.jpg", "shards" => hashes, "mime_type" => "image/jpeg" assert last_response.ok?, "Should have got status 200 but got #{last_response.status}" last_response.content_type.must_equal "application/json;charset=utf-8" box = job1.reload.images first = box.contents.first box.contents.length.must_equal current_count+1 first.image.src.must_match %r{^(.+)/rose\.jpg$} required_response = { :position => 0, :entry => first.export } Spot::JSON.parse(last_response.body).must_equal required_response end end describe "/page" do before do @update_slug = "/@spontaneous/page/#{project1.id}/slug" @page = project1 end it "return json for individual pages" do page = site.home.children.first auth_get "/@spontaneous/page/#{page.id}" assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" assert_equal S::JSON.encode(page.export), last_response.body end it "respect user levels in page json" do page = AdminAccess.create auth_get "/@spontaneous/page/#{page.id}" result = Spot::JSON.parse(last_response.body) result[:fields].map { |f| f[:name] }.must_equal ["title"] end it "allows editing of paths" do @page.path.must_equal '/project1' auth_put @update_slug, 'slug' => 'howabout' assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" @page.reload @page.path.must_equal "/howabout" Spot::JSON.parse(last_response.body).must_equal({:path => '/howabout', :slug => 'howabout' }) end it "raises an error when trying to save duplicate paths" do auth_put @update_slug, 'slug' => 'project2' last_response.status.must_equal 409 @page.reload.path.must_equal '/project1' end it "raises an error when trying to save an empty slug" do auth_put @update_slug, 'slug' => '' last_response.status.must_equal 406 @page.reload.path.must_equal '/project1' auth_put @update_slug last_response.status.must_equal 406 @page.reload.path.must_equal '/project1' end it "provides a list of unavailable slugs for a page" do auth_get "#{@update_slug}/unavailable" assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" Spot::JSON.parse(last_response.body).must_equal %w(project2 project3) end it "allows for syncing slugs with the page title" do @page.title = "This is Project" @page.save auth_put "#{@update_slug}/sync" assert last_response.ok? last_response.content_type.must_equal "application/json;charset=utf-8" @page.reload @page.path.must_equal "/this-is-project" Spot::JSON.parse(last_response.body).must_equal({:path => '/this-is-project', :slug => 'this-is-project' }) end it "allows UID editing by developer level users" do user.update(:level => Spontaneous::Permissions[:root]) uid = "fishy" project1.uid.wont_equal uid auth_put "/@spontaneous/page/#{project1.id}/uid", 'uid' => uid assert last_response.ok? Spot::JSON.parse(last_response.body).must_equal({:uid => uid}) project1.reload.uid.must_equal uid user.update(:level => Spontaneous::Permissions[:editor]) end it "disallows UID editing by non-developer level users" do uid = "boom" orig = project1.uid project1.uid.wont_equal uid auth_put "/@spontaneous/page/#{project1.id}/uid", 'uid' => uid assert last_response.status == 403 project1.reload.uid.must_equal orig end end describe "/changes" do before do S::Permissions::UserLevel[:editor].stubs(:can_publish?).returns(true) end it "be able to retrieve a serialised list of all unpublished changes" do auth_get "/@spontaneous/changes" assert last_response.ok?, "Expected 200 recieved #{last_response.status}" last_response.content_type.must_equal "application/json;charset=utf-8" last_response.body.must_equal S::Change.serialise_http(site) end it "be able to start a publish with a set of change sets" do site.expects(:publish_pages).with([project1.id]) auth_post "/@spontaneous/changes", :page_ids => [project1.id] assert last_response.ok?, "Expected 200 recieved #{last_response.status}" end it "not launch publish if list of changes is empty" do site.expects(:publish_pages).with().never auth_post "/@spontaneous/changes", :change_set_ids => "" assert last_response.status == 400, "Expected 400, recieved #{last_response.status}" auth_post "/@spontaneous/changes", :change_set_ids => nil assert last_response.status == 400 end it "recognise when the list of changes is complete" do site.expects(:publish_pages).with([home.id, project1.id]) auth_post "/@spontaneous/changes", :page_ids => [home.id, project1.id] assert last_response.ok?, "Expected 200 recieved #{last_response.status}" end end describe "/alias" do it "retrieves a list of potential targets" do auth_get "/@spontaneous/alias/#{LinkedJob.schema_id}/#{home.id}/#{home.in_progress.schema_id}" assert last_response.ok? expected = LinkedJob.targets(home, home.in_progress) response = Spot::JSON.parse(last_response.body) response[:pages].must_equal 1 response[:page].must_equal 1 response[:total].must_equal expected.length response[:targets].must_equal expected.map { |job| { :id => job.id, :title => job.title.to_s, :icon => job.image.export } } end it "filters targets using a search string" do auth_get "/@spontaneous/alias/#{LinkedJob.schema_id}/#{home.id}/#{home.in_progress.schema_id}", {"query" => "job 3"} assert last_response.ok? expected = [job3] response = Spot::JSON.parse(last_response.body) response[:pages].must_equal 1 response[:page].must_equal 1 response[:total].must_equal expected.length response[:targets].must_equal expected.map { |job| { :id => job.id, :title => job.title.to_s, :icon => job.image.export } } end it "adds an alias to a box" do home.featured_jobs.contents.length.must_equal 0 auth_post "/@spontaneous/alias/#{home.id}/#{HomePage.boxes[:featured_jobs].schema_id.to_s}", 'alias_id' => LinkedJob.schema_id.to_s, 'target_ids' => Job.first.id, "position" => 0 assert last_response.ok?, "Recieved #{last_response.status} not 200" last_response.content_type.must_equal "application/json;charset=utf-8" home.reload home.featured_jobs.contents.length.must_equal 1 a = home.featured_jobs.first assert a.alias? a.target.must_equal Job.first required_response = { :position => 0, :entry => home.featured_jobs.contents.first.export(user) } Spot::JSON.parse(last_response.body).first.must_equal required_response end it "allows for adding multiple aliases to a box" do home.featured_jobs.contents.length.must_equal 0 jobs = Job.all[0..1] auth_post "/@spontaneous/alias/#{home.id}/#{HomePage.boxes[:featured_jobs].schema_id.to_s}", 'alias_id' => LinkedJob.schema_id.to_s, 'target_ids' => jobs.map(&:id), "position" => 0 assert last_response.ok?, "Recieved #{last_response.status} not 200" home.reload home.featured_jobs.contents.length.must_equal 2 home.featured_jobs.each_with_index do |a, i| assert a.alias? a.target.must_equal jobs[i] end response = Spot::JSON.parse(last_response.body) response[0][:position].must_equal 0 response[1][:position].must_equal 1 response[0][:entry].must_equal home.featured_jobs[0].export(user) response[1][:entry].must_equal home.featured_jobs[1].export(user) end it "adds an alias to a box at any position" do home.featured_jobs << Job.new home.featured_jobs << Job.new home.featured_jobs << Job.new home.save.reload home.featured_jobs.contents.length.must_equal 3 auth_post "/@spontaneous/alias/#{home.id}/#{HomePage.boxes[:featured_jobs].schema_id.to_s}", 'alias_id' => LinkedJob.schema_id.to_s, 'target_ids' => Job.first.id, "position" => 2 assert last_response.ok?, "Recieved #{last_response.status} not 200" last_response.content_type.must_equal "application/json;charset=utf-8" home.reload home.featured_jobs.contents.length.must_equal 4 a = home.featured_jobs[2] assert a.alias? a.target.must_equal Job.first required_response = { :position => 2, :entry => home.featured_jobs[2].export(user) } Spot::JSON.parse(last_response.body).first.must_equal required_response end it "interfaces with lists of non-content targets" do begin @target_id = target_id = 9999 @target = target = mock() @target.stubs(:id).returns(@target_id) @target.stubs(:title).returns("custom object") @target.stubs(:to_json).returns({:title => "custom object", :id => @target_id}.to_json) @target.stubs(:alias_title).returns("custom object") @target.stubs(:exported_alias_icon).returns(nil) ::LinkedSomething = Class.new(Piece) do alias_of proc { [target] }, :lookup => lambda { |id| return target if id == target_id nil } end box = home.boxes[:featured_jobs] box._prototype.allow LinkedSomething auth_post "/@spontaneous/alias/#{home.id}/#{box.schema_id.to_s}", 'alias_id' => LinkedSomething.schema_id.to_s, 'target_ids' => @target_id, "position" => 0 assert last_response.status == 200, "Expected a 200 but got #{last_response.status}" home.reload a = home.featured_jobs[0] assert a.alias? a.target.must_equal @target ensure Object.send(:remove_const, LinkedSomething) rescue nil end end end describe "/asset" do it "return scripts from js dir" do get '/@spontaneous/js/test.js' assert last_response.ok?, "Expected a 200 but received a #{last_response.status}" last_response.content_type.must_equal "application/javascript; charset=UTF-8" # Sprockets appends sone newlines and a semicolon onto our test file assert_equal File.read(@app_dir / 'js/test.js') + "\n;\n", last_response.body end it "work for site public files" do get "/test.html" assert last_response.ok? assert_equal (<<-HTML).gsub(/^\s+/, ''), last_response.body