omgdav 0.0.0

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.
Files changed (54) hide show
  1. data/.document +3 -0
  2. data/.gitignore +17 -0
  3. data/.manifest +53 -0
  4. data/.wrongdoc.yml +6 -0
  5. data/COPYING +661 -0
  6. data/ChangeLog +185 -0
  7. data/GIT-VERSION-FILE +1 -0
  8. data/GIT-VERSION-GEN +33 -0
  9. data/GNUmakefile +35 -0
  10. data/LATEST +1 -0
  11. data/NEWS +1 -0
  12. data/README +154 -0
  13. data/bin/omgdav-setup +4 -0
  14. data/bin/omgdav-sync +32 -0
  15. data/lib/omgdav/app.rb +70 -0
  16. data/lib/omgdav/copy.rb +100 -0
  17. data/lib/omgdav/copy_move.rb +54 -0
  18. data/lib/omgdav/db.rb +258 -0
  19. data/lib/omgdav/delete.rb +66 -0
  20. data/lib/omgdav/get.rb +35 -0
  21. data/lib/omgdav/http_get.rb +146 -0
  22. data/lib/omgdav/input_wrapper.rb +32 -0
  23. data/lib/omgdav/migrations/0001_initial.rb +45 -0
  24. data/lib/omgdav/migrations/0002_contenttype.rb +15 -0
  25. data/lib/omgdav/migrations/0003_synctmp.rb +14 -0
  26. data/lib/omgdav/mkcol.rb +28 -0
  27. data/lib/omgdav/move.rb +74 -0
  28. data/lib/omgdav/options.rb +21 -0
  29. data/lib/omgdav/propfind.rb +46 -0
  30. data/lib/omgdav/propfind_response.rb +150 -0
  31. data/lib/omgdav/proppatch.rb +116 -0
  32. data/lib/omgdav/put.rb +110 -0
  33. data/lib/omgdav/rack_util.rb +56 -0
  34. data/lib/omgdav/setup.rb +16 -0
  35. data/lib/omgdav/sync.rb +78 -0
  36. data/lib/omgdav/version.rb +2 -0
  37. data/lib/omgdav.rb +27 -0
  38. data/omgdav.gemspec +35 -0
  39. data/pkg.mk +175 -0
  40. data/setup.rb +1586 -0
  41. data/test/integration.rb +232 -0
  42. data/test/test_copy.rb +121 -0
  43. data/test/test_delete.rb +15 -0
  44. data/test/test_litmus.rb +61 -0
  45. data/test/test_move.rb +66 -0
  46. data/test/test_omgdav_app.rb +102 -0
  47. data/test/test_propfind.rb +30 -0
  48. data/test/test_proppatch.rb +156 -0
  49. data/test/test_put.rb +31 -0
  50. data/test/test_readonly.rb +22 -0
  51. data/test/test_sync.rb +49 -0
  52. data/test/test_urlmap.rb +59 -0
  53. data/test/test_worm.rb +26 -0
  54. metadata +342 -0
@@ -0,0 +1,232 @@
1
+ # Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
2
+ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
3
+ $stdout.sync = $stderr.sync = true
4
+ Thread.abort_on_exception = true
5
+ if ENV["COVERAGE"]
6
+ require "coverage"
7
+ Coverage.start
8
+ at_exit do
9
+ # Dirty little text formatter. I tried simplecov but the default
10
+ # HTML+JS is unusable without a GUI (I hate GUIs :P) and it would've
11
+ # taken me longer to search the Internets to find a plain-text
12
+ # formatter I like...
13
+ res = Coverage.result
14
+ relevant = res.keys.grep(%r{/lib/omgdav/\w+\.rb})
15
+ relevant.each do |file|
16
+ cov = res[file]
17
+ puts "==> #{file} <=="
18
+ File.readlines(file).each_with_index do |line, i|
19
+ n = cov[i]
20
+ if n == 0 # BAD
21
+ print(" *** 0 #{line}")
22
+ elsif n
23
+ printf("% 7u %s", n, line)
24
+ elsif line =~ /\S/ # probably a line with just "end" in it
25
+ print(" #{line}")
26
+ else # blank line
27
+ print "\n" # don't output trailing whitespace on blank lines
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ require 'pp'
35
+ require 'minitest/autorun'
36
+ require 'net/http'
37
+ require 'uri'
38
+ require 'tempfile'
39
+ require 'stringio'
40
+ require 'logger'
41
+ require 'mogilefs'
42
+ require 'open-uri'
43
+ require 'rack'
44
+ require 'rack/mock'
45
+ require 'omgdav/app'
46
+ require 'nokogiri'
47
+ require 'time'
48
+ require 'json'
49
+ Sequel.extension(:migration)
50
+
51
+ module TestMogileFSIntegration
52
+ def x(*cmd)
53
+ out = Tempfile.new("out")
54
+ err = Tempfile.new("err")
55
+ puts cmd.join(' ') if $VERBOSE
56
+ pid = fork do
57
+ $stderr.reopen(err.path, "a")
58
+ $stdout.reopen(out.path, "a")
59
+ out.close
60
+ err.close
61
+ ObjectSpace.each_object(Tempfile) do |tmp|
62
+ next if tmp.closed?
63
+ ObjectSpace.undefine_finalizer(tmp)
64
+ tmp.close_on_exec = true if tmp.respond_to?(:close_on_exec=)
65
+ end
66
+ exec(*cmd)
67
+ end
68
+ _, status = Process.waitpid2(pid)
69
+ out.rewind
70
+ err.rewind
71
+ [ status, out, err ]
72
+ end
73
+
74
+ def x!(*cmd)
75
+ status, out, err = x(*cmd)
76
+ assert status.success?, "#{status.inspect} / #{out.read} / #{err.read}"
77
+ [ status, out, err ]
78
+ end
79
+
80
+ def setup_mogilefs(plugins = nil, mogstored = "mogstored")
81
+ @test_host = "127.0.0.1"
82
+ setup_mogstored(mogstored)
83
+ @tracker = TCPServer.new(@test_host, 0)
84
+ @tracker_port = @tracker.addr[1]
85
+
86
+ @dbname = Tempfile.new(["mogfresh", ".sqlite3"])
87
+ @mogilefsd_conf = Tempfile.new(["mogilefsd", "conf"])
88
+ @mogilefsd_pid = Tempfile.new(["mogilefsd", "pid"])
89
+
90
+ cmd = %w(mogdbsetup --yes --type=SQLite --dbname) << @dbname.path
91
+ x!(*cmd)
92
+
93
+ @mogilefsd_conf.puts "db_dsn DBI:SQLite:#{@dbname.path}"
94
+ @mogilefsd_conf.write <<EOF
95
+ conf_port #@tracker_port
96
+ listen #@test_host
97
+ pidfile #{@mogilefsd_pid.path}
98
+ replicate_jobs 1
99
+ fsck_jobs 1
100
+ query_jobs 1
101
+ mogstored_stream_port #@mogstored_mgmt_port
102
+ node_timeout 10
103
+ EOF
104
+ @mogilefsd_conf.flush
105
+
106
+ @trackers = @hosts = [ "#@test_host:#@tracker_port" ]
107
+ @tracker.close
108
+ x!("mogilefsd", "--daemon", "--config=#{@mogilefsd_conf.path}")
109
+ wait_for_port @tracker_port
110
+ @admin = MogileFS::Admin.new(:hosts => @hosts)
111
+ 500.times do
112
+ break if File.size(@mogstored_pid.path) > 0
113
+ sleep 0.05
114
+ end
115
+
116
+ args = { :ip => @test_host, :port => @mogstored_http_port }
117
+ args[:status] = "alive"
118
+ @admin.create_host("me", args)
119
+ yield_for_monitor_update { @admin.get_hosts.empty? or break }
120
+
121
+ wait_for_usage_file "dev1"
122
+ wait_for_usage_file "dev2"
123
+ @admin.create_device("me", 1)
124
+ @admin.create_device("me", 2)
125
+ @admin.create_domain("testdom")
126
+ yield_for_monitor_update { @admin.get_devices.size == 2 and break }
127
+ @mogc = MogileFS::MogileFS.new(:hosts => @hosts, :domain => "testdom")
128
+ end
129
+
130
+ def yield_for_monitor_update
131
+ 50.times do
132
+ yield
133
+ sleep 0.1
134
+ end
135
+ end
136
+
137
+ def wait_for_port(port)
138
+ tries = 500
139
+ begin
140
+ TCPSocket.new(@test_host, port).close
141
+ return
142
+ rescue
143
+ sleep 0.05
144
+ end while (tries -= 1) > 0
145
+ raise "#@test_host:#{port} never became ready"
146
+ end
147
+
148
+ def teardown
149
+ if @mogstored_pid
150
+ pid = File.read(@mogstored_pid.path).to_i
151
+ Process.kill(:TERM, pid) if pid > 0
152
+ end
153
+ if @mogilefsd_pid
154
+ s = TCPSocket.new(@test_host, @tracker_port)
155
+ s.write "!shutdown\r\n"
156
+ s.close
157
+ end
158
+ FileUtils.rmtree(@docroot)
159
+ end
160
+
161
+ def wait_for_usage_file(device)
162
+ uri = URI("http://#@test_host:#@mogstored_http_port/#{device}/usage")
163
+ res = nil
164
+ 100.times do
165
+ res = Net::HTTP.get_response(uri)
166
+ if Net::HTTPOK === res
167
+ puts res.body if $DEBUG
168
+ return
169
+ end
170
+ puts res.inspect if $DEBUG
171
+ sleep 0.1
172
+ end
173
+ raise "#{uri} failed to appear: #{res.inspect}"
174
+ end
175
+
176
+ def setup_mogstored(mogstored = "mogstored")
177
+ @docroot = Dir.mktmpdir(["mogfresh", "docroot"])
178
+ Dir.mkdir("#@docroot/dev1")
179
+ Dir.mkdir("#@docroot/dev2")
180
+ @mogstored_mgmt = TCPServer.new(@test_host, 0)
181
+ @mogstored_http = TCPServer.new(@test_host, 0)
182
+ @mogstored_mgmt_port = @mogstored_mgmt.addr[1]
183
+ @mogstored_http_port = @mogstored_http.addr[1]
184
+ @mogstored_conf = Tempfile.new(["mogstored", "conf"])
185
+ @mogstored_pid = Tempfile.new(["mogstored", "pid"])
186
+ @mogstored_conf.write <<EOF
187
+ pidfile = #{@mogstored_pid.path}
188
+ maxconns = 500
189
+ httplisten = #@test_host:#@mogstored_http_port
190
+ mgmtlisten = #@test_host:#@mogstored_mgmt_port
191
+ docroot = #@docroot
192
+ EOF
193
+ @mogstored_conf.flush
194
+ @mogstored_mgmt.close
195
+ @mogstored_http.close
196
+
197
+ x!(mogstored, "--daemon", "--config=#{@mogstored_conf.path}")
198
+ wait_for_port @mogstored_mgmt_port
199
+ wait_for_port @mogstored_http_port
200
+ end
201
+
202
+ def setup
203
+ setup_mogilefs
204
+ @err = StringIO.new
205
+ logger = Logger.new(@err)
206
+ logger = Logger.new($stderr)
207
+ @opts = { "rack.logger" => logger }
208
+ @db_file = Tempfile.new(%w(omgdav .sqlite))
209
+ @db = Sequel.connect("sqlite://#{@db_file.path}")
210
+ @db.pragma_set(:synchronous, :off)
211
+ @migdir = File.dirname(File.dirname(__FILE__)) + "/lib/omgdav/migrations"
212
+ Sequel::Migrator.apply(@db, @migdir, nil)
213
+ @app = OMGDAV::App.new(@db, @mogc)
214
+ @req = Rack::MockRequest.new(@app)
215
+ end
216
+
217
+ def body_string(body)
218
+ rv = ""
219
+ body.each { |chunk| rv << chunk }
220
+ rv
221
+ end
222
+
223
+ # Rack::MockRequest doesn't always work since it converts the
224
+ # response into an array without dup-ing Strings, so response
225
+ # bodies which reuse a buffer (when they assume they're writing
226
+ # to a socket) fail
227
+ def req(method, uri, opts = {})
228
+ o = { method: method }.merge!(opts)
229
+ env = @req.class.env_for(uri, o)
230
+ @app.call(env)
231
+ end
232
+ end
data/test/test_copy.rb ADDED
@@ -0,0 +1,121 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
3
+ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
4
+ require './test/integration'
5
+ class TestCopy < MiniTest::Unit::TestCase
6
+ include TestMogileFSIntegration
7
+
8
+ def test_copy_file
9
+ # create a file
10
+ resp = @req.put("/hello", input: StringIO.new("HELLO"))
11
+ assert_equal 201, resp.status.to_i
12
+
13
+ resp = @req.put("/goodbye", input: StringIO.new("GOODBYE"))
14
+ assert_equal 201, resp.status.to_i
15
+
16
+ opts = {
17
+ "HTTP_DESTINATION" => 'http://example.com/bar',
18
+ "HTTP_HOST" => 'example.com',
19
+ }
20
+ status, _, _ = req("COPY", "/hello", opts)
21
+ assert_equal 201, status.to_i
22
+
23
+ status, _, body = req("GET", "/bar")
24
+ assert_equal 200, status.to_i
25
+ assert_equal "HELLO", body_string(body)
26
+
27
+ opts = {
28
+ "HTTP_DESTINATION" => 'http://example.com/bar',
29
+ "HTTP_HOST" => 'example.com',
30
+ }
31
+ status, _, _ = req("COPY", "/goodbye", opts)
32
+ assert_equal 204, status.to_i
33
+
34
+ status, _, body = req("GET", "/bar")
35
+ assert_equal 200, status.to_i
36
+ assert_equal "GOODBYE", body_string(body)
37
+
38
+ opts = {
39
+ "HTTP_DESTINATION" => 'http://example.com/bar',
40
+ "HTTP_HOST" => 'example.com',
41
+ }
42
+ status, _, _ = req("COPY", "/bar", opts)
43
+ assert_equal 403, status.to_i
44
+
45
+ opts = {
46
+ "HTTP_DESTINATION" => 'http://example.com/a/b',
47
+ "HTTP_HOST" => 'example.com:80',
48
+ }
49
+ status, _, _ = req("COPY", "/bar", opts)
50
+ assert_equal 409, status.to_i
51
+
52
+ assert_equal 201, req("MKCOL", "/a")[0].to_i
53
+
54
+ opts = {
55
+ "HTTP_DESTINATION" => 'http://example.com/a/b',
56
+ "HTTP_HOST" => 'example.com:80',
57
+ }
58
+ status, _, _ = req("COPY", "/bar", opts)
59
+ assert_equal 201, status.to_i
60
+ status, _, body = req("GET", "/a/b")
61
+ assert_equal 200, status.to_i
62
+ assert_equal "GOODBYE", body_string(body)
63
+
64
+ opts = {
65
+ 'HTTP_DESTINATION' => 'http://example.com/a/b',
66
+ 'HTTP_HOST' => 'example.com',
67
+ 'HTTP_OVERWRITE' => 'F',
68
+ }
69
+ status, _, _ = req("COPY", "/hello", opts)
70
+ assert_equal 412, status.to_i
71
+ status, _, body = req("GET", "/a/b")
72
+ assert_equal 200, status.to_i
73
+ assert_equal "GOODBYE", body_string(body)
74
+
75
+ opts = {
76
+ 'HTTP_DESTINATION' => 'http://www.example.com/a/b',
77
+ 'HTTP_HOST' => 'example.com',
78
+ }
79
+ assert_equal 502, req("COPY", "/hello", opts)[0].to_i
80
+
81
+ opts = {
82
+ 'HTTP_DESTINATION' => 'http://example.com/a/b',
83
+ 'HTTP_HOST' => 'example.com:8080',
84
+ }
85
+ assert_equal 502, req("COPY", "/hello", opts)[0].to_i
86
+ end
87
+
88
+ def test_copy_collection_empty
89
+ assert_equal 201, req("MKCOL", "/a")[0].to_i
90
+
91
+ opts = {
92
+ 'HTTP_DESTINATION' => 'http://example.com/b',
93
+ 'HTTP_HOST' => 'example.com',
94
+ }
95
+ assert_equal 201, req("COPY", "/a", opts)[0].to_i
96
+ assert @db[:paths][name: "b"][:collection]
97
+
98
+ assert_equal 204, req("COPY", "/a", opts)[0].to_i
99
+ end
100
+
101
+ def test_copy_collection_full
102
+ assert_equal 201, req("MKCOL", "/a")[0].to_i
103
+ assert_equal 201, req("MKCOL", "/a/b")[0].to_i
104
+ assert_equal 201, @req.put("/a/c", input: StringIO.new("a")).status.to_i
105
+ assert_equal 201, @req.put("/a/b/c", input: StringIO.new("C")).status.to_i
106
+
107
+ opts = {
108
+ 'HTTP_DESTINATION' => 'http://example.com/b',
109
+ 'HTTP_HOST' => 'example.com',
110
+ }
111
+ assert_equal 201, req("COPY", "/a", opts)[0].to_i
112
+
113
+ status, _, body = req("GET", "/b/c")
114
+ assert_equal 200, status.to_i
115
+ assert_equal "a", body_string(body)
116
+
117
+ status, _, body = req("GET", "/b/b/c")
118
+ assert_equal 200, status.to_i
119
+ assert_equal "C", body_string(body)
120
+ end
121
+ end
@@ -0,0 +1,15 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
3
+ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
4
+ require './test/integration'
5
+ class TestDelete < MiniTest::Unit::TestCase
6
+ include TestMogileFSIntegration
7
+
8
+ def test_delete
9
+ status, _, _ = req("DELETE", "/non-existent")
10
+ assert_equal 404, status.to_i
11
+
12
+ status, _, _ = req("DELETE", "/")
13
+ assert_equal 403, status.to_i
14
+ end
15
+ end
@@ -0,0 +1,61 @@
1
+ # Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
2
+ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
3
+ require "./test/integration"
4
+ require "unicorn"
5
+
6
+ LITMUS_DIR = ENV["LITMUS_DIR"]
7
+ class TestLitmus < MiniTest::Unit::TestCase
8
+ include TestMogileFSIntegration
9
+
10
+ def setup
11
+ super
12
+ @uni_err = Tempfile.new(%w(stderr .log))
13
+ @ru = Tempfile.new(%w(omgdav .ru))
14
+ @ru.sync = true
15
+ @ru.write <<EOF
16
+ require 'omgdav/app'
17
+ db = Sequel.connect("sqlite://#{@db_file.path}")
18
+ db.pragma_set(:synchronous, :off)
19
+ mogc = MogileFS::MogileFS.new(hosts: #{@hosts.inspect}, domain: "testdom")
20
+ run OMGDAV::App.new(db, mogc)
21
+ EOF
22
+ uni_sock = TCPServer.new(@test_host, 0)
23
+ port = uni_sock.addr[1]
24
+ @url = "http://#@test_host:#{port}/"
25
+ @uni_pid = fork do
26
+ ENV["UNICORN_FD"] = uni_sock.fileno.to_s
27
+ ENV["RACK_ENV"] = "deployment"
28
+ $stderr.reopen(@uni_err.path, "a")
29
+ uni = Unicorn::HttpServer.new(@app, listeners: ["#@test_host:#{port}"])
30
+ uni.start.join
31
+ end
32
+ uni_sock.close
33
+ end
34
+
35
+ def test_litmus
36
+ cmd = %W(#{ENV["MAKE"] || 'make'} -C #{LITMUS_DIR} URL=#@url check)
37
+ out = Tempfile.new("err")
38
+ err = Tempfile.new("out")
39
+ pid = fork do
40
+ $stdout.reopen(out)
41
+ $stderr.reopen(err)
42
+ exec(*cmd)
43
+ end
44
+ _, status = Process.waitpid2(pid)
45
+ if $DEBUG || $VERBOSE
46
+ out.rewind
47
+ err.rewind
48
+ puts out.read
49
+ puts err.read
50
+ end
51
+ assert status.success?, status.inspect
52
+ end
53
+
54
+ def teardown
55
+ Process.kill(:QUIT, @uni_pid)
56
+ _, status = Process.waitpid2(@uni_pid)
57
+ assert status.success?, status.inspect
58
+ puts @uni_err.read if $DEBUG || $VERBOSE
59
+ super
60
+ end
61
+ end if LITMUS_DIR
data/test/test_move.rb ADDED
@@ -0,0 +1,66 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
3
+ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
4
+ require './test/integration'
5
+ class TestMove < MiniTest::Unit::TestCase
6
+ include TestMogileFSIntegration
7
+
8
+ def test_move_file
9
+ # create a file
10
+ resp = @req.put("/hello", input: StringIO.new("HELLO"))
11
+ assert_equal 201, resp.status.to_i
12
+ resp = @req.put("/goodbye", input: StringIO.new("GOODBYE"))
13
+ assert_equal 201, resp.status.to_i
14
+
15
+ opts = {
16
+ "HTTP_DESTINATION" => 'http://example.com/bar',
17
+ "HTTP_HOST" => 'example.com',
18
+ }
19
+ status, _, _ = req("MOVE", "/hello", opts)
20
+ assert_equal 201, status.to_i
21
+
22
+ status, headers, body = req("GET", "/bar")
23
+ assert_equal 200, status.to_i
24
+ assert_equal "5", headers["Content-Length"]
25
+ assert_equal "HELLO", body_string(body)
26
+
27
+ assert_equal 404, req("GET", "/hello")[0].to_i
28
+
29
+ opts = {
30
+ "HTTP_DESTINATION" => 'http://example.com/bar',
31
+ "HTTP_HOST" => 'example.com',
32
+ }
33
+ status, _, _ = req("MOVE", "/goodbye", opts)
34
+ assert_equal 204, status.to_i
35
+ end
36
+
37
+ def test_move_collection
38
+ assert_equal 201, req("MKCOL", "/a")[0].to_i
39
+ assert_equal 201, req("MKCOL", "/a/b")[0].to_i
40
+ (0..9).each do |i|
41
+ input = StringIO.new(i.to_s)
42
+ assert_equal 201, @req.put("/a/#{i}", input: input).status.to_i
43
+ input.rewind
44
+ assert_equal 201, @req.put("/a/b/#{i}", input: input).status.to_i
45
+ end
46
+
47
+ opts = {
48
+ "HTTP_DESTINATION" => 'http://example.com/c',
49
+ "HTTP_HOST" => 'example.com',
50
+ }
51
+ status, _, _ = req("MOVE", "/a", opts)
52
+ assert_equal 201, status.to_i
53
+ (0..9).each do |i|
54
+ assert_equal 404, @req.get("/a/#{i}").status.to_i
55
+ assert_equal 404, @req.get("/a/b/#{i}").status.to_i
56
+
57
+ status, _, body = req("GET", "/c/#{i}")
58
+ assert_equal 200, status.to_i
59
+ assert_equal i.to_s, body_string(body)
60
+
61
+ status, _, body = req("GET", "/c/b/#{i}")
62
+ assert_equal 200, status.to_i
63
+ assert_equal i.to_s, body_string(body)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,102 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
3
+ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
4
+ require './test/integration'
5
+ class Test_OMGDAV_App < MiniTest::Unit::TestCase
6
+ include TestMogileFSIntegration
7
+
8
+ def test_basic
9
+ # create a file
10
+ resp = @req.put("/foo", input: StringIO.new("HELLO"))
11
+ assert_equal 201, resp.status.to_i
12
+
13
+ # make sure it got into MogileFS
14
+ info = @mogc.file_info("foo")
15
+ assert_equal 5, info["length"]
16
+ assert_equal "HELLO", @mogc.get_file_data("foo")
17
+
18
+ # ensure we can get it back
19
+ status, headers, body = req("GET", "/foo")
20
+ assert_equal 200, status.to_i
21
+ assert_equal "5", headers["Content-Length"]
22
+ assert_equal "HELLO", body_string(body)
23
+ mtime = Time.parse(headers["Last-Modified"])
24
+ assert_nil body.close
25
+
26
+ # ensure we can get a range back
27
+ status, headers, body = req("GET", "/foo", "HTTP_RANGE" => "bytes=1-")
28
+ assert_equal 206, status.to_i
29
+ assert_equal "ELLO", body_string(body)
30
+ assert_nil body.close
31
+
32
+ status, headers, body = req("PROPFIND", "/foo", "HTTP_DEPTH" => "0")
33
+ assert_equal 207, status.to_i
34
+ assert_match(%r{\Atext/xml\b}, headers["Content-Type"])
35
+ foo0 = body_string(body)
36
+
37
+ status, headers, body = req("PROPFIND", "/foo", "HTTP_DEPTH" => "1")
38
+ assert_equal 207, status.to_i
39
+ assert_match(%r{\Atext/xml\b}, headers["Content-Type"])
40
+ foo1 = body_string(body)
41
+ assert_equal foo0, foo1
42
+
43
+ # ensure we can get a PROPFIND with Depth: 0
44
+ status, headers, body = req("PROPFIND", "/", "HTTP_DEPTH" => "0")
45
+ assert_equal 207, status.to_i
46
+ xml = Nokogiri::XML(body_string(body))
47
+ responses = xml.search("//D:multistatus/D:response")
48
+ assert_equal 1, responses.size
49
+ root = responses[0]
50
+ assert_equal "/", root.search("//D:href").text
51
+ assert_equal 1, root.search(".//D:collection").size
52
+
53
+ # PROPFIND with Depth:1
54
+ status, headers, body = req("PROPFIND", "/", "HTTP_DEPTH" => "1")
55
+ assert_equal 207, status.to_i
56
+ xml = Nokogiri::XML(body = body_string(body))
57
+ responses = xml.search("//D:multistatus/D:response")
58
+ assert_equal 2, responses.size
59
+ assert_equal root.to_s, responses[0].to_s
60
+ foo = responses[1]
61
+ refute_equal foo.to_s, root.to_s
62
+ assert_equal "/foo", foo.search(".//D:href").text
63
+ clen = foo.search(".//lp1:getcontentlength", "lp1" => "DAV:")
64
+ assert_equal '5', clen.text
65
+ modified = foo.search(".//lp1:getlastmodified", "lp1" => "DAV:")
66
+ assert_equal mtime, Time.httpdate(modified.text)
67
+ created= foo.search(".//lp1:creationdate", "lp1" => "DAV:")
68
+ assert_equal mtime, Time.xmlschema(created.text)
69
+ assert_empty foo.search(".//D:collection")
70
+
71
+ # PROPFIND with Depth:infinity
72
+ status, headers, body = req("PROPFIND", "/", "HTTP_DEPTH" => "infinity")
73
+ assert_equal 400, status.to_i
74
+
75
+ # PROPFIND with unspecified Depth (assumed infinity)
76
+ status, headers, body = req("PROPFIND", "/")
77
+ assert_equal 400, status.to_i
78
+ end
79
+
80
+ def test_deep_delete
81
+ assert_equal 201, req("MKCOL", "/a")[0].to_i
82
+ assert_equal 201, req("MKCOL", "/a/b")[0].to_i
83
+ assert_equal 201, req("MKCOL", "/a/b/c")[0].to_i
84
+ assert_equal 201, req("MKCOL", "/a/b/c/d")[0].to_i
85
+ range = (0..9)
86
+ range.each do |i|
87
+ resp = @req.put("/a/b/c/d/#{i}", input: StringIO.new("#{i}"))
88
+ assert_equal 201, resp.status.to_i
89
+ assert @mogc.exist?("a/b/c/d/#{i}")
90
+
91
+ resp = @req.put("/a/#{i}", input: StringIO.new("#{i}"))
92
+ assert_equal 201, resp.status.to_i
93
+ assert @mogc.exist?("a/#{i}")
94
+ end
95
+ assert_equal 204, req("DELETE", "/a")[0].to_i
96
+ range.each do |i|
97
+ refute @mogc.exist?("a/b/c/d/#{i}")
98
+ refute @mogc.exist?("a/#{i}")
99
+ end
100
+ assert_equal [ @app.root_node ], @db[:paths].to_a
101
+ end
102
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
3
+ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
4
+ require './test/integration'
5
+ class TestPropfind < MiniTest::Unit::TestCase
6
+ include TestMogileFSIntegration
7
+
8
+ def test_invalid_input
9
+ opts = {
10
+ "HTTP_DEPTH" => "0",
11
+ input: StringIO.new("<foo>bar")
12
+ }
13
+ assert_equal 400, req("PROPFIND", "/", opts)[0].to_i
14
+ end
15
+
16
+ def test_dir
17
+ opts = { "HTTP_DEPTH" => "0" }
18
+ assert_equal 201, req("MKCOL", "/a")[0].to_i
19
+
20
+ status, _, body = req("PROPFIND", "/a", opts)
21
+ assert_equal 207, status.to_i
22
+ a0 = body_string(body)
23
+
24
+ opts["HTTP_DEPTH"] = "1"
25
+ status, _, body = req("PROPFIND", "/a", opts)
26
+ assert_equal 207, status.to_i
27
+ a1 = body_string(body)
28
+ assert_equal a0, a1
29
+ end
30
+ end