Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions tests/providers/nextcloud/test_provider.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import io
import asyncio
from http import client

import pytest
Expand All @@ -11,6 +12,7 @@
from waterbutler.providers.nextcloud.metadata import (NextcloudFileMetadata,
NextcloudFileRevisionMetadata)

from unittest import mock
from tests import utils
from tests.providers.nextcloud.fixtures import (
provider,
Expand Down Expand Up @@ -599,3 +601,138 @@ def test_shares_storage_root(self, provider, provider_different_credentials):

def test_can_duplicate_names(self, provider):
assert provider.can_duplicate_names()


class FilePathFactory:
def __init__(self, _href):
self._href = _href
self.is_file = True


class TestMetadataFolder:

@pytest.mark.asyncio
@pytest.mark.aiohttpretty
async def test_metadata_folder_path_is_dir(self, provider, file_metadata, file_revision_metadata, file_metadata_object,
file_metadata_2, file_checksum, file_checksum_2, file_checksum_3):

# Define shared values
checksum_api_path = 'apps/checksum_api/api/checksum?path=/my_folder/dissertation.aux&hash=md5,sha256,sha512'
revision_path = '&revision=1591864889'

# Setup path for the _metadata_folder method
path = WaterButlerPath('/dissertation.aux', prepend=provider.folder)

# Setup url for call metadata
url = provider._webdav_url_ + path.full_path
aiohttpretty.register_uri('PROPFIND', url, body=file_metadata, auto_length=True, status=207)

# Setup url and call revision
url = provider._dav_url_ + 'versions/' + provider.credentials['username'] + '/versions/' + file_metadata_object.fileid
aiohttpretty.register_uri('PROPFIND', url, body=file_revision_metadata, auto_length=True, status=207)

# Setup url and call checksum api for file_checksum
checksum_url = provider._ocs_url + checksum_api_path
aiohttpretty.register_uri('GET', checksum_url, body=file_checksum, auto_length=True, status=200)

# Setup url and call checksum api for file_checksum_2
checksum_url = provider._ocs_url + checksum_api_path + revision_path
aiohttpretty.register_uri('GET', checksum_url, body=file_checksum_2, auto_length=True, status=200)

# Call checksum api for file_checksum_3
aiohttpretty.register_uri('GET', checksum_url, body=file_checksum_3, auto_length=True, status=200)

# Setup return value for mock
future = asyncio.Future()
future.set_result([FilePathFactory('/my_folder/dissertation.aux')])

with mock.patch('waterbutler.providers.nextcloud.utils.parse_dav_response', return_value=future):
result = await provider._metadata_folder(path)

# Assert result
assert isinstance(result, list)
assert len(result) > 0


class TestRevisionsNextCloud:

@pytest.mark.asyncio
@pytest.mark.aiohttpretty
async def test_revisions_nextcloud(self, provider, file_metadata, file_revision_metadata, file_metadata_object,
file_checksum, file_checksum_2, file_checksum_3):
# Setup path for the _metadata_folder method
path = WaterButlerPath('/dissertation.aux', prepend=provider.folder)

# Setup url for call metadata
url = provider._webdav_url_ + path.full_path
aiohttpretty.register_uri('PROPFIND', url, body=file_metadata, auto_length=True, status=207)

# Setup url for call revision
url = provider._dav_url_ + 'versions/' + provider.credentials['username'] + '/versions/' + file_metadata_object.fileid
aiohttpretty.register_uri('PROPFIND', url, body=file_revision_metadata, auto_length=True, status=207)

# Call checksum api for file_checksum_1
checksum_url = provider._ocs_url + 'apps/checksum_api/api/checksum?path=/my_folder/dissertation.aux&hash=md5,sha256,sha512'
aiohttpretty.register_uri('GET', checksum_url, body=file_checksum, auto_length=True, status=200)

# Call checksum api for file_checksum_2
checksum_url = provider._ocs_url + 'apps/checksum_api/api/checksum?path=/my_folder/dissertation.aux&hash=md5,sha256,sha512&revision=1591876099'
aiohttpretty.register_uri('GET', checksum_url, body=file_checksum_2, auto_length=True, status=200)

# Call checksum api for file_checksum_3
checksum_url = provider._ocs_url + 'apps/checksum_api/api/checksum?path=/my_folder/dissertation.aux&hash=md5,sha256,sha512&revision=1591864889'
aiohttpretty.register_uri('GET', checksum_url, body=file_checksum_3, auto_length=True, status=200)

result = await provider.revisions(path)

assert isinstance(result, list)
assert len(result) == 3
assert isinstance(result[0], NextcloudFileRevisionMetadata)
assert isinstance(result[1], NextcloudFileRevisionMetadata)
assert isinstance(result[2], NextcloudFileRevisionMetadata)

assert result[0].modified == 'Sun, 10 Jul 2016 23:28:31 GMT'
assert result[0].version == 'a3c411808d58977a9ecd7485b5b7958e'
assert result[0].version_identifier == 'revision'

@pytest.mark.asyncio
@pytest.mark.aiohttpretty
async def test_revisions_nextcloud_when_have_more_than_one_version(self, provider, file_metadata, file_revision_metadata, file_metadata_object,
file_checksum, file_checksum_2, file_checksum_3):
# Define shared values
checksum_api_path = 'apps/checksum_api/api/checksum?path=/my_folder/dissertation.aux&hash=md5,sha256,sha512'
revision_path = '&revision=1591864889'

# Setup path for the _metadata_folder method
path = WaterButlerPath('/dissertation.aux', prepend=provider.folder)

# Setup url for call metadata
url = provider._webdav_url_ + path.full_path
aiohttpretty.register_uri('PROPFIND', url, body=file_metadata, auto_length=True, status=207)

# Setup url for call revision
url = provider._dav_url_ + 'versions/' + provider.credentials['username'] + '/versions/' + file_metadata_object.fileid
aiohttpretty.register_uri('PROPFIND', url, body=file_revision_metadata, auto_length=True, status=207)

# Setup url and call checksum api for file_checksum
checksum_url = provider._ocs_url + checksum_api_path
aiohttpretty.register_uri('GET', checksum_url, body=file_checksum, auto_length=True, status=200)

# Setup url and call checksum api for file_checksum_2
checksum_url = provider._ocs_url + checksum_api_path + revision_path
aiohttpretty.register_uri('GET', checksum_url, body=file_checksum_2, auto_length=True, status=200)

# Call checksum api for file_checksum_3
aiohttpretty.register_uri('GET', checksum_url, body=file_checksum_3, auto_length=True, status=200)

# Setup return value for mock
future = asyncio.Future()
future.set_result([FilePathFactory('/my_folder/dissertation.aux'), FilePathFactory('/my_folder/source.aux')])

with mock.patch('waterbutler.providers.nextcloud.utils.parse_dav_response', return_value=future):
result = await provider._metadata_revision(path)

# Assert result
assert isinstance(result, list)
assert len(result) == 2

145 changes: 72 additions & 73 deletions waterbutler/providers/nextcloud/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ async def validate_v1_path(self, path, **kwargs):
full_path = WaterButlerPath(path, prepend=self.folder)

response = await self.make_request('PROPFIND',
self._webdav_url_ + full_path.full_path,
self._webdav_url_ + full_path.full_path.replace(' ', '%20'),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spaceのみをURLencodeする意図は何でしょうか?
逆に他の文字はエスケープする必要はないのでしょうか?

expects=(200, 207, 404),
throws=exceptions.MetadataError,
auth=self._auth,
Expand Down Expand Up @@ -177,7 +177,7 @@ async def download(self, path, accept_url=False, range=None, **kwargs):
if revision is None:
download_resp = await self.make_request(
'GET',
self._webdav_url_ + path.full_path,
self._webdav_url_ + path.full_path.replace(' ', '%20'),
range=range,
expects=(200, 206,),
throws=exceptions.DownloadError,
Expand Down Expand Up @@ -214,7 +214,7 @@ async def upload(self, stream, path, conflict='replace', **kwargs):

response = await self.make_request(
'PUT',
self._webdav_url_ + path.full_path,
self._webdav_url_ + path.full_path.replace(' ', '%20'),
data=stream,
headers={'Content-Length': str(stream.size)},
expects=(201, 204,),
Expand All @@ -234,7 +234,7 @@ async def delete(self, path, **kwargs):
"""
delete_resp = await self.make_request(
'DELETE',
self._webdav_url_ + path.full_path,
self._webdav_url_ + path.full_path.replace(' ', '%20'),
expects=(204,),
throws=exceptions.DeleteError,
auth=self._auth,
Expand Down Expand Up @@ -267,7 +267,7 @@ async def _metadata_folder(self, path, skip_first=True, **kwargs):
* 207: Multipart response
"""
response = await self.make_request('PROPFIND',
self._webdav_url_ + path.full_path,
self._webdav_url_ + path.full_path.replace(' ', '%20'),
expects=(204, 207),
throws=exceptions.MetadataError,
auth=self._auth,
Expand All @@ -280,36 +280,37 @@ async def _metadata_folder(self, path, skip_first=True, **kwargs):
items = await utils.parse_dav_response(self.NAME, content, self.folder, skip_first)
await response.release()

for i in items:
if i.is_file and self.NAME == 'nextcloudinstitutions':
params = {
'path': i._href,
'hash': 'md5,sha256,sha512'
}
response = await self.make_request('GET',
self._ocs_url + 'apps/checksum_api/api/checksum',
params=params,
expects=(200, 404),
throws=exceptions.MetadataError,
auth=self._auth,
connector=self.connector(),
headers={'OCS-APIRequest': 'true'}
)

if response.status == 200:
content = await response.content.read()
extra = {}
extra['hashes'] = await utils.parse_checksum_response(content)
i.extra = extra
await response.release()
if not path.is_dir:
for i in items:
if i.is_file:
params = {
'path': i._href,
'hash': 'md5,sha256,sha512'
}
response = await self.make_request('GET',
self._ocs_url + 'apps/checksum_api/api/checksum',
params=params,
expects=(200, 404),
throws=exceptions.MetadataError,
auth=self._auth,
connector=self.connector(),
headers={'OCS-APIRequest': 'true'}
)

if response.status == 200:
content = await response.content.read()
extra = {}
extra['hashes'] = await utils.parse_checksum_response(content)
i.extra = extra
await response.release()

return items

async def _metadata_revision(self, path):
query = '<?xml version="1.0" encoding="UTF-8"?> <d:propfind xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns" > <d:prop xmlns:oc="http://owncloud.org/ns"> <d:getlastmodified/> <d:getcontentlength/> <d:resourcetype/> <d:getetag/> <d:getcontenttype/> <oc:fileid/> </d:prop> </d:propfind>'

response = await self.make_request('PROPFIND',
self._webdav_url_ + path.full_path,
self._webdav_url_ + path.full_path.replace(' ', '%20'),
data=query,
expects=(204, 207),
throws=exceptions.MetadataError,
Expand All @@ -326,27 +327,26 @@ async def _metadata_revision(self, path):
if len(items) != 1:
return items

if self.NAME == 'nextcloudinstitutions':
params = {
'path': path.full_path,
'hash': 'md5,sha256,sha512'
}
response = await self.make_request('GET',
self._ocs_url + 'apps/checksum_api/api/checksum',
params=params,
expects=(200, 404),
throws=exceptions.MetadataError,
auth=self._auth,
connector=self.connector(),
headers={'OCS-APIRequest': 'true'}
)
params = {
'path': path.full_path,
'hash': 'md5,sha256,sha512'
}
response = await self.make_request('GET',
self._ocs_url + 'apps/checksum_api/api/checksum',
params=params,
expects=(200, 404),
throws=exceptions.MetadataError,
auth=self._auth,
connector=self.connector(),
headers={'OCS-APIRequest': 'true'}
)

if response.status == 200:
content = await response.content.read()
extra = {}
extra['hashes'] = await utils.parse_checksum_response(content)
items[0].extra = extra
await response.release()
if response.status == 200:
content = await response.content.read()
extra = {}
extra['hashes'] = await utils.parse_checksum_response(content)
items[0].extra = extra
await response.release()

fileid = items[0].fileid

Expand All @@ -364,29 +364,28 @@ async def _metadata_revision(self, path):
revision_items = await utils.parse_dav_response(self.NAME, content, self.folder, True)
await response.release()

if self.NAME == 'nextcloudinstitutions':
for rev in revision_items:
params = {
'path': path.full_path,
'hash': 'md5,sha256,sha512',
'revision': str(rev.etag)
}
response = await self.make_request('GET',
self._ocs_url + 'apps/checksum_api/api/checksum',
params=params,
expects=(200, 404),
throws=exceptions.MetadataError,
auth=self._auth,
connector=self.connector(),
headers={'OCS-APIRequest': 'true'}
)

if response.status == 200:
content = await response.content.read()
extra = {}
extra['hashes'] = await utils.parse_checksum_response(content)
rev.extra = extra
await response.release()
for rev in revision_items:
params = {
'path': path.full_path,
'hash': 'md5,sha256,sha512',
'revision': str(rev.etag)
}
response = await self.make_request('GET',
self._ocs_url + 'apps/checksum_api/api/checksum',
params=params,
expects=(200, 404),
throws=exceptions.MetadataError,
auth=self._auth,
connector=self.connector(),
headers={'OCS-APIRequest': 'true'}
)

if response.status == 200:
content = await response.content.read()
extra = {}
extra['hashes'] = await utils.parse_checksum_response(content)
rev.extra = extra
await response.release()

items.extend(revision_items)

Expand Down Expand Up @@ -446,12 +445,12 @@ async def _do_dav_move_copy(self, src_path, dest_path, operation):

resp = await self.make_request(
operation,
self._webdav_url_ + src_path.full_path,
self._webdav_url_ + src_path.full_path.replace(' ', '%20'),
expects=(201, 204), # WebDAV MOVE/COPY: 201 = Created, 204 = Updated existing
throws=exceptions.IntraCopyError,
auth=self._auth,
connector=self.connector(),
headers={'Destination': '/remote.php/webdav' + dest_path.full_path}
headers={'Destination': '/remote.php/webdav' + dest_path.full_path.replace(' ', '%20')}
)
await resp.release()

Expand Down
2 changes: 1 addition & 1 deletion waterbutler/server/api/v1/provider/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async def prepare(self, *args, **kwargs):
method = self.request.method.lower()

# TODO Find a nicer way to handle this
if method == 'options':
if method == 'options' or self.path_kwargs['path'].endswith(('.css', '.map')):
return

self.arguments = {
Expand Down
4 changes: 4 additions & 0 deletions waterbutler/server/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ async def write_stream(self, stream):
# Temp fix, write does not accept bytearrays currently
if isinstance(chunk, bytearray):
chunk = bytes(chunk)
self.set_header('Content-Length', str(len(chunk)))
if hasattr(stream, 'size'):
if stream.size > len(chunk):
self.set_header('Content-Length', str(stream.size))
self.write(chunk)
self.bytes_downloaded += len(chunk)
del chunk
Expand Down