copy 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +9 -0
- data/Rakefile +10 -0
- data/copy.gemspec +26 -0
- data/lib/copy/admin/copy.css +56 -0
- data/lib/copy/admin/edit.html.erb +12 -0
- data/lib/copy/admin/index.html.erb +44 -0
- data/lib/copy/admin/index.js.erb +53 -0
- data/lib/copy/admin/jquery.js +18 -0
- data/lib/copy/router.rb +62 -0
- data/lib/copy/server.rb +118 -0
- data/lib/copy/storage/mongodb.rb +39 -0
- data/lib/copy/storage/redis.rb +19 -0
- data/lib/copy/storage/relational.rb +37 -0
- data/lib/copy/storage.rb +30 -0
- data/lib/copy/version.rb +3 -0
- data/lib/copy.rb +6 -0
- data/test/router_test.rb +108 -0
- data/test/server_test.rb +167 -0
- data/test/storage_test.rb +50 -0
- data/test/test_app/views/about/index.html.erb +1 -0
- data/test/test_app/views/about/us.html.erb +1 -0
- data/test/test_app/views/data/people.csv.erb +1 -0
- data/test/test_app/views/data/people.xml.erb +1 -0
- data/test/test_app/views/index.html.erb +1 -0
- data/test/test_app/views/layout.html.erb +8 -0
- data/test/test_app/views/with_copy_helper.html.erb +3 -0
- data/test/test_app/views/with_copy_helper_one_line.html.erb +1 -0
- data/test/test_helper.rb +49 -0
- metadata +152 -0
data/lib/copy/server.rb
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'sinatra/base'
|
|
2
|
+
require 'erb'
|
|
3
|
+
require 'redcarpet'
|
|
4
|
+
|
|
5
|
+
module Copy
|
|
6
|
+
class Server < Sinatra::Base
|
|
7
|
+
set :views, './views'
|
|
8
|
+
set :public, './public'
|
|
9
|
+
set :root, File.dirname(File.expand_path(__FILE__))
|
|
10
|
+
|
|
11
|
+
helpers do
|
|
12
|
+
def protected!
|
|
13
|
+
unless authorized?
|
|
14
|
+
response['WWW-Authenticate'] = %(Basic realm="Copy Admin Area")
|
|
15
|
+
throw(:halt, [401, "Not authorized\n"])
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def authorized?
|
|
20
|
+
return false unless settings.respond_to?(:admin_user) && settings.respond_to?(:admin_password)
|
|
21
|
+
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
|
22
|
+
@auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == [settings.admin_user, settings.admin_password]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def set_cache_control_header
|
|
26
|
+
if settings.respond_to?(:cache_time) && settings.cache_time.is_a?(Numeric) && settings.cache_time > 0
|
|
27
|
+
expires settings.cache_time, :public
|
|
28
|
+
else
|
|
29
|
+
cache_control :no_cache
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def format_text(name, content, options = {})
|
|
34
|
+
original = content.dup
|
|
35
|
+
# Apply markdown formatting.
|
|
36
|
+
content = Redcarpet.new(content, :smart).to_html.chomp
|
|
37
|
+
|
|
38
|
+
html_attrs = %Q(class="_copy_editable" data-name="#{name}")
|
|
39
|
+
|
|
40
|
+
if original =~ /\n/ # content with newlines renders in a div
|
|
41
|
+
tag = options[:wrap_tag] || :div
|
|
42
|
+
%Q(<#{tag} #{html_attrs}>#{content}</#{tag}>)
|
|
43
|
+
else # single line content renders in a span without <p> tags
|
|
44
|
+
tag = options[:wrap_tag] || :span
|
|
45
|
+
content.gsub!(/<\/*p>/, '')
|
|
46
|
+
%Q(<#{tag} #{html_attrs}>#{content}</#{tag}>)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def copy(name, options = {}, &block)
|
|
51
|
+
if !Copy::Storage.connected? || !(content = Copy::Storage.get(name))
|
|
52
|
+
# Side-step the output buffer so we can capture the block, but not output it.
|
|
53
|
+
@_out_buf, old_buffer = '', @_out_buf
|
|
54
|
+
content = yield
|
|
55
|
+
@_out_buf = old_buffer
|
|
56
|
+
|
|
57
|
+
# Get the first line from captured text.
|
|
58
|
+
first_line = content.split("\n").first
|
|
59
|
+
# Determine how much white space it has in front.
|
|
60
|
+
white_space = first_line.match(/^(\s)*/)[0]
|
|
61
|
+
# Remove that same amount of white space from the beginning of every line.
|
|
62
|
+
content.gsub!(Regexp.new("^#{white_space}"), '')
|
|
63
|
+
|
|
64
|
+
# Save the content so it can be edited.
|
|
65
|
+
Copy::Storage.set(name, content) if Copy::Storage.connected?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Append the output buffer.
|
|
69
|
+
@_out_buf << format_text(name, content, options)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.config(&block)
|
|
74
|
+
class_eval(&block)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
before do
|
|
78
|
+
if settings.respond_to?(:storage) && !Copy::Storage.connected?
|
|
79
|
+
Copy::Storage.connect!(settings.storage)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
get '/_copy/?' do
|
|
84
|
+
protected!
|
|
85
|
+
ERB.new(File.read(settings.root + '/admin/index.html.erb')).result(self.send(:binding))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
get '/_copy.js' do
|
|
89
|
+
protected!
|
|
90
|
+
content_type(:js)
|
|
91
|
+
ERB.new(File.read(settings.root + '/admin/index.js.erb')).result(self.send(:binding))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
get '/_copy/:name' do
|
|
95
|
+
protected!
|
|
96
|
+
@name = params[:name]
|
|
97
|
+
@doc = Copy::Storage.get(params[:name])
|
|
98
|
+
ERB.new(File.read(settings.root + '/admin/edit.html.erb')).result(self.send(:binding))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
put '/_copy/:name' do
|
|
102
|
+
protected!
|
|
103
|
+
Copy::Storage.set(params[:name], params[:content])
|
|
104
|
+
format_text(params[:name], Copy::Storage.get(params[:name]), :wrap_tag => params[:wrap_tag])
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
get '*' do
|
|
108
|
+
route = Copy::Router.new(params[:splat].first, settings.views)
|
|
109
|
+
if route.success?
|
|
110
|
+
set_cache_control_header
|
|
111
|
+
content_type(route.format)
|
|
112
|
+
send(route.renderer, route.template, :layout => route.layout)
|
|
113
|
+
else
|
|
114
|
+
not_found
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'mongo'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
4
|
+
module Copy
|
|
5
|
+
module Storage
|
|
6
|
+
class Mongodb
|
|
7
|
+
def initialize(connection_url)
|
|
8
|
+
uri = URI.parse(connection_url)
|
|
9
|
+
connection = ::Mongo::Connection.from_uri(connection_url)
|
|
10
|
+
database = connection.db(uri.path.gsub(/^\//, ''))
|
|
11
|
+
|
|
12
|
+
@collection = database['copy-content']
|
|
13
|
+
@collection.ensure_index([['name', Mongo::ASCENDING]], :unique => true)
|
|
14
|
+
@collection
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get(name)
|
|
18
|
+
doc = find(name)
|
|
19
|
+
doc['content'] unless doc.nil?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def set(name, content)
|
|
23
|
+
doc = find(name)
|
|
24
|
+
if doc
|
|
25
|
+
doc['content'] = content
|
|
26
|
+
@collection.update({ '_id' => doc['_id'] }, doc)
|
|
27
|
+
else
|
|
28
|
+
@collection.insert('name' => name, 'content' => content)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
def find(name)
|
|
34
|
+
docs = @collection.find('name' => name)
|
|
35
|
+
docs.first if docs.respond_to?(:first)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'redis'
|
|
2
|
+
|
|
3
|
+
module Copy
|
|
4
|
+
module Storage
|
|
5
|
+
class Redis
|
|
6
|
+
def initialize(connection_url)
|
|
7
|
+
@redis = ::Redis.new(connection_url)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def get(name)
|
|
11
|
+
@redis.hget("copy:content", name)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def set(name, content)
|
|
15
|
+
@redis.hset("copy:content", name, content)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'data_mapper'
|
|
2
|
+
|
|
3
|
+
module Copy
|
|
4
|
+
module Storage
|
|
5
|
+
class Relational
|
|
6
|
+
class Document
|
|
7
|
+
include DataMapper::Resource
|
|
8
|
+
|
|
9
|
+
storage_names[:default] = 'copy_documents'
|
|
10
|
+
|
|
11
|
+
property :id, Serial
|
|
12
|
+
property :name, String, :unique_index => true
|
|
13
|
+
property :content, Text
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(connection_url)
|
|
17
|
+
DataMapper.setup(:default, connection_url)
|
|
18
|
+
DataMapper.finalize
|
|
19
|
+
DataMapper.auto_upgrade!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get(name)
|
|
23
|
+
doc = Document.first(:name => name)
|
|
24
|
+
doc.content unless doc.nil?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def set(name, content)
|
|
28
|
+
doc = Document.first(:name => name)
|
|
29
|
+
if doc
|
|
30
|
+
doc.update(:content => content)
|
|
31
|
+
else
|
|
32
|
+
Document.create(:name => name, :content => content)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/copy/storage.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
module Copy
|
|
4
|
+
module Storage
|
|
5
|
+
autoload :Mongodb, 'copy/storage/mongodb'
|
|
6
|
+
autoload :Redis, 'copy/storage/redis'
|
|
7
|
+
autoload :Relational, 'copy/storage/relational'
|
|
8
|
+
|
|
9
|
+
def self.connect!(connection_url)
|
|
10
|
+
scheme = URI.parse(connection_url).scheme
|
|
11
|
+
klass = scheme.capitalize
|
|
12
|
+
if %w(sqlite mysql postgres).include?(scheme)
|
|
13
|
+
klass = 'Relational'
|
|
14
|
+
end
|
|
15
|
+
@@storage = Copy::Storage.const_get(klass).new(connection_url)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.connected?
|
|
19
|
+
!defined?(@@storage).nil?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.get(name)
|
|
23
|
+
@@storage.get(name.to_s)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.set(name, content)
|
|
27
|
+
@@storage.set(name.to_s, content)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/copy/version.rb
ADDED
data/lib/copy.rb
ADDED
data/test/router_test.rb
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class RouterTest < Test::Unit::TestCase
|
|
4
|
+
test "format defaults to html" do
|
|
5
|
+
assert_equal :html, router('/something').format
|
|
6
|
+
assert_equal :html, router('/a/b/c/').format
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
test "recognize format in path" do
|
|
10
|
+
assert_equal :html, router('/something.html').format
|
|
11
|
+
assert_equal :html, router('/about/something.html').format
|
|
12
|
+
assert_equal :xml, router('/index.xml').format
|
|
13
|
+
assert_equal :csv, router('/employees.csv').format
|
|
14
|
+
assert_equal :rss, router('/news/feed.rss').format
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
test "find template file" do
|
|
18
|
+
Dir.expects(:glob).with('./views/about.html*').twice.returns(['./views/about.html.erb'])
|
|
19
|
+
|
|
20
|
+
assert_equal './views/about.html.erb', router('/about', './views').template_file
|
|
21
|
+
assert_equal './views/about.html.erb', router('/about.html', './views').template_file
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test "find template file as index in dir" do
|
|
25
|
+
Dir.expects(:glob).with('./views/about.html*').returns([])
|
|
26
|
+
Dir.expects(:glob).with('./views/about/index.html*').returns(['./views/about/index.html.erb'])
|
|
27
|
+
|
|
28
|
+
assert_equal './views/about/index.html.erb', router('/about', './views').template_file
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
test "find template file as index in dir when path has trailing slash" do
|
|
32
|
+
Dir.expects(:glob).with('./views/about/us.html*').returns([])
|
|
33
|
+
Dir.expects(:glob).with('./views/about/us/index.html*').returns(['./views/about/us/index.html.erb'])
|
|
34
|
+
|
|
35
|
+
assert_equal './views/about/us/index.html.erb', router('/about/us/', './views').template_file
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
test "find template file in dir when path has trailing slash" do
|
|
39
|
+
Dir.expects(:glob).with('./views/about/us.html*').returns(['./views/about/us.html.erb'])
|
|
40
|
+
|
|
41
|
+
assert_equal './views/about/us.html.erb', router('/about/us/', './views').template_file
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
test "find index template file when empty path given" do
|
|
45
|
+
Dir.expects(:glob).with('./views/index.html*').returns(['./views/index.html.erb'])
|
|
46
|
+
|
|
47
|
+
assert_equal './views/index.html.erb', router('/', './views').template_file
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
test "renderer determined from template file extension" do
|
|
51
|
+
r1 = router('/about')
|
|
52
|
+
r1.expects(:template_file).returns('about.html.erb')
|
|
53
|
+
assert_equal :erb, r1.renderer
|
|
54
|
+
|
|
55
|
+
r2 = router('/about')
|
|
56
|
+
r2.expects(:template_file).returns('about.html.haml')
|
|
57
|
+
assert_equal :haml, r2.renderer
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
test "template determined from template file" do
|
|
61
|
+
r1 = router('/about', './views')
|
|
62
|
+
r1.expects(:template_file).at_least_once.returns('./views/about.html.erb')
|
|
63
|
+
assert_equal :'about.html', r1.template
|
|
64
|
+
|
|
65
|
+
r2 = router('/about/nothing.html', './views')
|
|
66
|
+
r2.expects(:template_file).at_least_once.returns('./views/about/nothing.html.erb')
|
|
67
|
+
assert_equal :'about/nothing.html', r2.template
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
test "layout for html format and presense of layout file" do
|
|
71
|
+
r = router('/about', './views')
|
|
72
|
+
r.expects(:template_file).returns('./views/about.html.erb')
|
|
73
|
+
File.expects(:exists?).with('./views/layout.html.erb').returns(true)
|
|
74
|
+
|
|
75
|
+
assert_equal :'layout.html', r.layout
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
test "layout is false when no layout file is found" do
|
|
79
|
+
r = router('/about', './views')
|
|
80
|
+
r.expects(:template_file).returns('./views/about.html.erb')
|
|
81
|
+
File.expects(:exists?).with('./views/layout.html.erb').returns(false)
|
|
82
|
+
|
|
83
|
+
assert_equal false, r.layout
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
test "layout is false with non-html format" do
|
|
87
|
+
r = router('/people.csv', './views')
|
|
88
|
+
assert_equal false, r.layout
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
test "success when template file found" do
|
|
92
|
+
Dir.expects(:glob).with('./views/about.html*').returns(['./views/about.html.erb'])
|
|
93
|
+
|
|
94
|
+
assert router('/about', './views').success?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
test "no success when template file not found" do
|
|
98
|
+
Dir.expects(:glob).with('./views/about.html*').returns([])
|
|
99
|
+
Dir.expects(:glob).with('./views/about/index.html*').returns([])
|
|
100
|
+
|
|
101
|
+
assert !router('/about', './views').success?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
def router(path, views = '')
|
|
106
|
+
Copy::Router.new(path, views)
|
|
107
|
+
end
|
|
108
|
+
end
|
data/test/server_test.rb
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require 'rack/test'
|
|
3
|
+
|
|
4
|
+
class ServerTest < Test::Unit::TestCase
|
|
5
|
+
include CopyAppSetup
|
|
6
|
+
include Rack::Test::Methods
|
|
7
|
+
|
|
8
|
+
test "GET index" do
|
|
9
|
+
get '/'
|
|
10
|
+
|
|
11
|
+
assert last_response.ok?
|
|
12
|
+
assert_equal 'text/html;charset=utf-8', last_response.headers['Content-Type']
|
|
13
|
+
assert_match "<title>I'm the layout!</title>", last_response.body
|
|
14
|
+
assert_match "<p>I'm the index!</p>", last_response.body
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
test "GET path with index in folder" do
|
|
18
|
+
%w(/about /about/).each do |path|
|
|
19
|
+
get path
|
|
20
|
+
|
|
21
|
+
assert last_response.ok?
|
|
22
|
+
assert_match "<p>About!</p>", last_response.body
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
test "GET path to template in folder" do
|
|
27
|
+
%w(/about/us /about/us/).each do |path|
|
|
28
|
+
get path
|
|
29
|
+
|
|
30
|
+
assert last_response.ok?
|
|
31
|
+
assert_match "<p>About us!</p>", last_response.body
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
test "GET non-existent path" do
|
|
36
|
+
get '/nope'
|
|
37
|
+
assert last_response.status == 404
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
test "cache_time setting sets a Cache-Control header" do
|
|
41
|
+
app.config { set :cache_time, 456 }
|
|
42
|
+
get '/'
|
|
43
|
+
assert_equal 'public, max-age=456', last_response.headers['Cache-Control']
|
|
44
|
+
|
|
45
|
+
[0, nil, false].each do |time|
|
|
46
|
+
app.config { set :cache_time, time }
|
|
47
|
+
get '/'
|
|
48
|
+
assert_equal 'no-cache', last_response.headers['Cache-Control']
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
test "GET csv" do
|
|
53
|
+
get 'data/people.csv'
|
|
54
|
+
|
|
55
|
+
assert last_response.ok?
|
|
56
|
+
assert_equal 'text/csv;charset=utf-8', last_response.headers['Content-Type']
|
|
57
|
+
assert_equal File.read(app.settings.views + '/data/people.csv.erb'), last_response.body
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
test "GET xml" do
|
|
61
|
+
get 'data/people.xml'
|
|
62
|
+
|
|
63
|
+
assert last_response.ok?
|
|
64
|
+
assert_equal 'application/xml;charset=utf-8', last_response.headers['Content-Type']
|
|
65
|
+
assert_equal File.read(app.settings.views + '/data/people.xml.erb'), last_response.body
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
test "connects to storage when setting present" do
|
|
69
|
+
connection_url = 'redis://localhost:1234'
|
|
70
|
+
app.config { set :storage, connection_url }
|
|
71
|
+
Copy::Storage.expects(:connect!).with(connection_url).once.returns(true)
|
|
72
|
+
get '/'
|
|
73
|
+
assert last_response.ok?
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class ServerCopyHelperTest < Test::Unit::TestCase
|
|
78
|
+
include CopyAppSetup
|
|
79
|
+
include Rack::Test::Methods
|
|
80
|
+
|
|
81
|
+
test "copy helper displays content from storage" do
|
|
82
|
+
Copy::Storage.stubs(:connected?).returns(true)
|
|
83
|
+
Copy::Storage.expects(:get).with(:facts).returns("truth")
|
|
84
|
+
|
|
85
|
+
get 'with_copy_helper'
|
|
86
|
+
assert last_response.ok?
|
|
87
|
+
assert_match "truth", last_response.body
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
test "copy helper saves defaults text when content is not in storage and renders it" do
|
|
91
|
+
Copy::Storage.stubs(:connected?).returns(true)
|
|
92
|
+
Copy::Storage.expects(:get).with(:facts).returns(nil)
|
|
93
|
+
Copy::Storage.expects(:set).with(:facts, "_Default Text_\n").returns(true)
|
|
94
|
+
|
|
95
|
+
get 'with_copy_helper'
|
|
96
|
+
assert last_response.ok?, last_response.errors
|
|
97
|
+
assert_match %Q(<div class="_copy_editable" data-name="facts"><p><em>Default Text</em></p></div>), last_response.body
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
test "copy helper shows default text when not connected" do
|
|
101
|
+
Copy::Storage.expects(:connected?).twice.returns(false)
|
|
102
|
+
|
|
103
|
+
get 'with_copy_helper'
|
|
104
|
+
assert last_response.ok?
|
|
105
|
+
assert_match %Q(<div class="_copy_editable" data-name="facts"><p><em>Default Text</em></p></div>), last_response.body
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
test "copy helper renders single line content correctly" do
|
|
109
|
+
Copy::Storage.expects(:connected?).twice.returns(false)
|
|
110
|
+
|
|
111
|
+
get 'with_copy_helper_one_line'
|
|
112
|
+
assert last_response.ok?
|
|
113
|
+
assert_match %Q(<span class="_copy_editable" data-name="headline">Important!</span>), last_response.body
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
class ServerAdminTest < Test::Unit::TestCase
|
|
118
|
+
include CopyAppSetup
|
|
119
|
+
include Rack::Test::Methods
|
|
120
|
+
|
|
121
|
+
test "GET /_copy is protected when no user/pass are set" do
|
|
122
|
+
get '/_copy'
|
|
123
|
+
assert_equal 401, last_response.status
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
test "GET /_copy protected when user/pass are set, but supplied incorrectly" do
|
|
127
|
+
setup_auth 'good', 'girl'
|
|
128
|
+
authorize 'bad', 'boy'
|
|
129
|
+
get '/_copy'
|
|
130
|
+
assert_equal 401, last_response.status
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
test "GET /_copy with valid credentials" do
|
|
134
|
+
authorize!
|
|
135
|
+
get '/_copy'
|
|
136
|
+
assert last_response.ok?
|
|
137
|
+
assert_match 'Edit Copy', last_response.body
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
test "GET /_copy.js" do
|
|
141
|
+
authorize!
|
|
142
|
+
get '/_copy.js'
|
|
143
|
+
assert last_response.ok?, last_response.errors
|
|
144
|
+
assert_match 'jQuery JavaScript Library', last_response.body
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
test "GET /_copy/:name" do
|
|
148
|
+
Copy::Storage.stubs(:connected?).returns(true)
|
|
149
|
+
Copy::Storage.expects(:get).with('fun').returns('party')
|
|
150
|
+
|
|
151
|
+
authorize!
|
|
152
|
+
get '/_copy/fun'
|
|
153
|
+
assert last_response.ok?, last_response.errors
|
|
154
|
+
assert_match "party</textarea>", last_response.body
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
test "PUT /_copy/:name" do
|
|
158
|
+
Copy::Storage.stubs(:connected?).returns(true)
|
|
159
|
+
Copy::Storage.expects(:set).with('fun', '_party_').returns(true)
|
|
160
|
+
Copy::Storage.expects(:get).with('fun').returns('_party_')
|
|
161
|
+
|
|
162
|
+
authorize!
|
|
163
|
+
put '/_copy/fun', :content => '_party_', :wrap_tag => 'article'
|
|
164
|
+
assert last_response.ok?, last_response.errors
|
|
165
|
+
assert_match %Q(<article class="_copy_editable" data-name="fun"><em>party</em></article>), last_response.body
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
# Maybe TODO: Should the tests require a running redis, mongo, mysql
|
|
4
|
+
# instance and actually test getting, setting data?
|
|
5
|
+
|
|
6
|
+
class StorageTest < Test::Unit::TestCase
|
|
7
|
+
test "mongodb connect!" do
|
|
8
|
+
connection_url ='mongodb://copy:secret@localhost/copy-content'
|
|
9
|
+
Copy::Storage::Mongodb.expects(:new).with(connection_url).returns(true)
|
|
10
|
+
|
|
11
|
+
assert Copy::Storage.connect!(connection_url)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
test "redis connect!" do
|
|
15
|
+
connection_url ='redis://localhost:6379'
|
|
16
|
+
Copy::Storage::Redis.expects(:new).with(connection_url).returns(true)
|
|
17
|
+
|
|
18
|
+
assert Copy::Storage.connect!(connection_url)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
test "mysql connect!" do
|
|
22
|
+
connection_url = 'mysql://localhost/copy_content'
|
|
23
|
+
Copy::Storage::Relational.expects(:new).with(connection_url).returns(true)
|
|
24
|
+
|
|
25
|
+
assert Copy::Storage.connect!(connection_url)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
test "postgres connect!" do
|
|
29
|
+
connection_url = 'postgres://localhost/copy_content'
|
|
30
|
+
Copy::Storage::Relational.expects(:new).with(connection_url).returns(true)
|
|
31
|
+
|
|
32
|
+
assert Copy::Storage.connect!(connection_url)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
test "sqlite connect!" do
|
|
36
|
+
connection_url = 'sqlite:///path/to/copy_content.db'
|
|
37
|
+
Copy::Storage::Relational.expects(:new).with(connection_url).returns(true)
|
|
38
|
+
|
|
39
|
+
assert Copy::Storage.connect!(connection_url)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
test "get and set" do
|
|
43
|
+
connection_url ='redis://localhost:6379'
|
|
44
|
+
Copy::Storage::Redis.expects(:new).with(connection_url).returns(stub(:get => :result1, :set => :result2))
|
|
45
|
+
|
|
46
|
+
Copy::Storage.connect!(connection_url)
|
|
47
|
+
assert_equal :result1, Copy::Storage.get('name')
|
|
48
|
+
assert_equal :result2, Copy::Storage.set('name', 'content')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p>About!</p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p>About us!</p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Javan,Zooey,Nali
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<person>Javan</person>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p>I'm the index!</p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1><% copy :headline do %>Important!<% end %></h1>
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
|
2
|
+
$LOAD_PATH.unshift dir + '/../lib'
|
|
3
|
+
|
|
4
|
+
require 'rubygems'
|
|
5
|
+
require 'test/unit'
|
|
6
|
+
begin
|
|
7
|
+
require 'turn'
|
|
8
|
+
rescue LoadError
|
|
9
|
+
end
|
|
10
|
+
require 'mocha'
|
|
11
|
+
require 'copy'
|
|
12
|
+
|
|
13
|
+
class Test::Unit::TestCase
|
|
14
|
+
def self.test(name, &block)
|
|
15
|
+
define_method("test_#{name.gsub(/\W/,'_')}", &block) if block
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.setup(&block)
|
|
19
|
+
define_method(:setup, &block)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.teardown(&block)
|
|
23
|
+
define_method(:teardown, &block)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module CopyAppSetup
|
|
28
|
+
def app
|
|
29
|
+
Copy::Server
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def setup
|
|
33
|
+
app.config do
|
|
34
|
+
set :views, File.dirname(File.expand_path(__FILE__)) + '/test_app/views'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def setup_auth(user, pass)
|
|
39
|
+
app.config do
|
|
40
|
+
set :admin_user, user
|
|
41
|
+
set :admin_password, pass
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def authorize!
|
|
46
|
+
setup_auth 'super', 'secret'
|
|
47
|
+
authorize 'super', 'secret'
|
|
48
|
+
end
|
|
49
|
+
end
|