omgdav 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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