From b1f1233fefd6ef97675c2161f8bb9566cb9e3ed6 Mon Sep 17 00:00:00 2001 From: Fitz Elliott Date: Tue, 29 Aug 2017 14:15:48 -0400 Subject: [PATCH] final cleanups for onedrive * If the resource root is not the same as the provider root, then we need to do some extra validation to make sure the user isn't requesting a file outside the configured resource root. OneDrive doesn't give us the parent pieces all at once, so we need to fetch the resource root and compare it to the path data. Assuming the request is valid, we need to take care to construct a proper OneDrivePath object, where the first path part is named '' and has the resource_root id, and all the remain parts are children of it. * add documentation, tests, test fixtures * standardize logging --- docs/provider.onedrive.rst | 8 + docs/providers.rst | 1 + tests/providers/onedrive/fixtures.py | 35 + .../providers/onedrive/fixtures/download.json | 227 +++++ tests/providers/onedrive/fixtures/paths.json | 53 ++ .../onedrive/fixtures/revisions.json | 56 ++ .../onedrive/fixtures/root_provider.json | 342 +++++++ .../onedrive/fixtures/subfolder_provider.json | 401 +++++++++ tests/providers/onedrive/test_metadata.py | 77 ++ tests/providers/onedrive/test_path.py | 114 +++ tests/providers/onedrive/test_provider.py | 851 +++++++++--------- waterbutler/core/exceptions.py | 7 + waterbutler/providers/onedrive/metadata.py | 63 +- waterbutler/providers/onedrive/path.py | 80 +- waterbutler/providers/onedrive/provider.py | 399 +++++--- waterbutler/providers/onedrive/settings.py | 5 +- 16 files changed, 2116 insertions(+), 603 deletions(-) create mode 100644 docs/provider.onedrive.rst create mode 100644 tests/providers/onedrive/fixtures.py create mode 100644 tests/providers/onedrive/fixtures/download.json create mode 100644 tests/providers/onedrive/fixtures/paths.json create mode 100644 tests/providers/onedrive/fixtures/revisions.json create mode 100644 tests/providers/onedrive/fixtures/root_provider.json create mode 100644 tests/providers/onedrive/fixtures/subfolder_provider.json create mode 100644 tests/providers/onedrive/test_metadata.py create mode 100644 tests/providers/onedrive/test_path.py diff --git a/docs/provider.onedrive.rst b/docs/provider.onedrive.rst new file mode 100644 index 000000000..75c3c7c3a --- /dev/null +++ b/docs/provider.onedrive.rst @@ -0,0 +1,8 @@ +OneDrive Provider +================= + +.. autoclass:: waterbutler.providers.onedrive.provider.OneDriveProvider + :members: + :undoc-members: + :show-inheritance: + :inherited-members: diff --git a/docs/providers.rst b/docs/providers.rst index 9635a8936..cff4c05e2 100644 --- a/docs/providers.rst +++ b/docs/providers.rst @@ -13,6 +13,7 @@ Providers provider.github provider.gitlab provider.googledrive + provider.onedrive provider.osfstorage provider.owncloud provider.cloudfiles diff --git a/tests/providers/onedrive/fixtures.py b/tests/providers/onedrive/fixtures.py new file mode 100644 index 000000000..8fd8b29b3 --- /dev/null +++ b/tests/providers/onedrive/fixtures.py @@ -0,0 +1,35 @@ +import os +import json +import pytest + + +@pytest.fixture +def root_provider_fixtures(): + # fixtures for testing validate_v1_path for root provider + with open(os.path.join(os.path.dirname(__file__), 'fixtures/root_provider.json'), 'r') as fp: + return json.load(fp) + + +@pytest.fixture +def subfolder_provider_fixtures(): + # fixtures for testing validate_v1_path for subfolder provider + with open(os.path.join(os.path.dirname(__file__), 'fixtures/subfolder_provider.json'), 'r') as fp: + return json.load(fp) + + +@pytest.fixture +def revision_fixtures(): + with open(os.path.join(os.path.dirname(__file__), 'fixtures/revisions.json'), 'r') as fp: + return json.load(fp) + + +@pytest.fixture +def download_fixtures(): + with open(os.path.join(os.path.dirname(__file__), 'fixtures/download.json'), 'r') as fp: + return json.load(fp) + + +@pytest.fixture +def path_fixtures(): + with open(os.path.join(os.path.dirname(__file__), 'fixtures/paths.json'), 'r') as fp: + return json.load(fp) diff --git a/tests/providers/onedrive/fixtures/download.json b/tests/providers/onedrive/fixtures/download.json new file mode 100644 index 000000000..662940767 --- /dev/null +++ b/tests/providers/onedrive/fixtures/download.json @@ -0,0 +1,227 @@ +{ + "root_id": "F4D50E400DFE7D4E!103", + "file_id": "F4D50E400DFE7D4E!291", + "file_revision": "aRjRENTBFNDAwREZFN0Q0RSEyOTEuMg", + "onenote_id": "F4D50E400DFE7D4E!154", + "onenote_revision": "aRjRENTBFNDAwREZFN0Q0RSExNTQuMg", + "file_download_url" : "https://public.bn1303.livefilestore.com/y4mB8JhDUWbofzVglNap3rO5i6R7jOQyJAz995dPlkrOiQeOV2jgK-EOf916z8YHi9A42WCTMVfNmHjJliYLccUFzJgsEK3j3cviT2YLlZBMRVN-sC0mfvZz_ZeDgiLzfSChMmNXkRoq6Ymh_F8r8jRAvZTzJOgyX3F7jdw4qcY27tz95Rutrl68W0Z8ntuh3bVoPIDHC5kckF8sSWoyv5j4BfRQCckjyrmaV8F1BM5Cb1x10WNdE7CP_X1bBFqY7ZTJzYcsQcDR07BdalvRTDp-A", + "file_revision_download_url": "https://kdqyaa.bn1303.livefilestore.com/y4pIg3zgkmcBaQ_b2CpkxiYLihuF-GaqI-zBWermrthafHBogxMCjK6Q2qA_DoELVL0-oogK2WBYfx_CKyjynFBkRe61e6OsMDAfn0NEo4fSLSXamfrMRZ0-Pyf8ZgUujCNHpaihbkj2hwIlvnNJez0ZDerAEdfA7jos7JQnVfEAU2GNXGnsyx9Yrn9VC72xLmeMdDh676UTL9gpG-2xj4BX1AI2Ro7phbbB1n2kwnwNRZOaK1tusL8cyUfjs7joD4X", + "file_content": "ten of them", + "file_revisions": { + "value" : [ + { + "@content.downloadUrl" : "https://kdqyaa.bn1303.livefilestore.com/y4pIg3zgkmcBaQ_b2CpkxiYLihuF-GaqI-zBWermrthafHBogxMCjK6Q2qA_DoELVL0-oogK2WBYfx_CKyjynFBkRe61e6OsMDAfn0NEo4fSLSXamfrMRZ0-Pyf8ZgUujCNHpaihbkj2hwIlvnNJez0ZDerAEdfA7jos7JQnVfEAU2GNXGnsyx9Yrn9VC72xLmeMdDh676UTL9gpG-2xj4BX1AI2Ro7phbbB1n2kwnwNRZOaK1tusL8cyUfjs7joD4X", + "createdDateTime" : "2017-08-17T17:49:39.613Z", + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-08-17T17:49:50.363Z", + "createdDateTime" : "2017-08-17T17:49:39.613Z" + }, + "name" : "toes.txt", + "size" : 11, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTEuMg", + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5MS4yNTg", + "createdBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + }, + "application" : { + "id" : "481710a4" + } + }, + "lastModifiedDateTime" : "2017-08-17T17:49:50.38Z", + "file" : { + "mimeType" : "text/plain", + "hashes" : { + "sha1Hash" : "D6FAC576DCF80198874C9C9476F021AF3F12688C" + } + }, + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giM", + "id" : "F4D50E400DFE7D4E!291", + "parentReference" : { + "id" : "F4D50E400DFE7D4E!103", + "name" : "root:", + "path" : "/drive/root:", + "driveId" : "f4d50e400dfe7d4e" + }, + "lastModifiedBy" : { + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + }, + "application" : { + "id" : "481710a4" + } + } + } + ], + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items", + "@odata.deltaLink" : "https://api.onedrive.com/v1.0/drives('me')/items('F4D50E400DFE7D4E!291')/view.delta?$top=250&token=aTE09NjM2Mzg1OTM3MTAzMzc7SUQ9RjRENTBFNDAwREZFN0Q0RSEyOTE7TFI9NjM2Mzg5MTk0MDUyMTA7RVA9MTY7U0k9ODA7U0c9MTtTTz0yO1BJPTM", + "@delta.token" : "aTE09NjM2Mzg1OTM3MTAzMzc7SUQ9RjRENTBFNDAwREZFN0Q0RSEyOTE7TFI9NjM2Mzg5MTk0MDUyMTA7RVA9MTY7U0k9ODA7U0c9MTtTTz0yO1BJPTM" + }, + "file_metadata" : { + "createdDateTime" : "2017-08-17T17:49:39.613Z", + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTEuMg", + "id" : "F4D50E400DFE7D4E!291", + "children" : [], + "lastModifiedBy" : { + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-08-17T17:49:50.363Z", + "createdDateTime" : "2017-08-17T17:49:39.613Z" + }, + "name" : "toes.txt", + "parentReference" : { + "driveId" : "f4d50e400dfe7d4e", + "path" : "/drive/root:", + "id" : "F4D50E400DFE7D4E!103" + }, + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "lastModifiedDateTime" : "2017-08-17T17:49:50.38Z", + "file" : { + "hashes" : { + "sha1Hash" : "D6FAC576DCF80198874C9C9476F021AF3F12688C" + }, + "mimeType" : "text/plain" + }, + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4mB8JhDUWbofzVglNap3rO5i6R7jOQyJAz995dPlkrOiQeOV2jgK-EOf916z8YHi9A42WCTMVfNmHjJliYLccUFzJgsEK3j3cviT2YLlZBMRVN-sC0mfvZz_ZeDgiLzfSChMmNXkRoq6Ymh_F8r8jRAvZTzJOgyX3F7jdw4qcY27tz95Rutrl68W0Z8ntuh3bVoPIDHC5kckF8sSWoyv5j4BfRQCckjyrmaV8F1BM5Cb1x10WNdE7CP_X1bBFqY7ZTJzYcsQcDR07BdalvRTDp-A", + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5MS4yNTg", + "createdBy" : { + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + }, + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + } + }, + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giM", + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21291')/children", + "size" : 11 + }, + "onenote_metadata": {"createdDateTime": "2017-02-23T07:27:44.253Z", "createdBy": {"application": {"id": "44048800", "displayName": "OneDrive website"}, "user": {"id": "f4d50e400dfe7d4e", "displayName": "Fitz Elliott"}}, "fileSystemInfo": {"createdDateTime": "2017-02-23T07:27:44.253Z", "lastModifiedDateTime": "2017-03-30T15:06:25.41Z"}, "children@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21154')/children", "children": [{"createdDateTime": "2017-02-23T07:28:14.373Z", "createdBy": {"user": {"id": "f4d50e400dfe7d4e", "displayName": "Fitz Elliott"}}, "fileSystemInfo": {"createdDateTime": "2017-02-23T07:28:14.373Z", "lastModifiedDateTime": "2017-02-23T07:28:30.647Z"}, "parentReference": {"name": "foo", "driveId": "f4d50e400dfe7d4e", "id": "F4D50E400DFE7D4E!154", "path": "/drive/root:/onenote/foo"}, "id": "F4D50E400DFE7D4E!157", "@content.downloadUrl": "https://iphwow.bn1303.livefilestore.com/y4mAB7VXKZCHWK5ib57BF1aaT_v4YLxoPS4xsR9Hw550zzwU56pPw_SC7OZbzDvHx5q5NTZ-BFQgfSuVgZ3vmURfkMOPRDmWC3vZiznhvfNNL46eBg9ZIFYNVpEolrUIvtsz_PjjlfUT0g_JA4qzGqCIMLPXLweE02hEVc9OfynPJiwx9vNNJpF1e021bkXv-kFEN3efR0uenDB-epaphmEQg", "name": "foo.one", "eTag": "aRjRENTBFNDAwREZFN0Q0RSExNTcuMjI", "size": 25474, "webUrl": "https://1drv.ms/o/s!AE59_g1ADtX0gR0", "file": {"mimeType": "application/msonenote"}, "lastModifiedBy": {"user": {"id": "f4d50e400dfe7d4e", "displayName": "Fitz Elliott"}}, "lastModifiedDateTime": "2017-02-23T07:28:30.663Z"}, {"createdDateTime": "2017-02-23T07:28:17.623Z", "createdBy": {"user": {"id": "f4d50e400dfe7d4e", "displayName": "Fitz Elliott"}}, "fileSystemInfo": {"createdDateTime": "2017-02-23T07:28:17.623Z", "lastModifiedDateTime": "2017-02-23T07:28:18.86Z"}, "parentReference": {"name": "foo", "driveId": "f4d50e400dfe7d4e", "id": "F4D50E400DFE7D4E!154", "path": "/drive/root:/onenote/foo"}, "id": "F4D50E400DFE7D4E!158", "@content.downloadUrl": "https://iphwow.bn1303.livefilestore.com/y4mpsZP831upa5BsCeniFzoWYj4vkU1Ul_M4VEi_QYvAeGoU-FiH9GiH9UzL9_cVYtPxgyLnyesMkeAFC9mjcbKNU8kMstHLbypJjKI5oiUGVjBoqUhKaVifBR1cTrKXkN4aQKmHjZce8O65IRgQHiT_HuPCSE4DmqnyPMwWCg36b1PLH2OsU1kww7HOLKxMb_eeBtNaJMUbZMk1CgJLVpDvA", "name": "Open Notebook.onetoc2", "eTag": "aRjRENTBFNDAwREZFN0Q0RSExNTguNA", "size": 2885, "webUrl": "https://1drv.ms/o/s!AE59_g1ADtX0gR4", "file": {"mimeType": "application/msonenote"}, "lastModifiedBy": {"user": {"id": "f4d50e400dfe7d4e", "displayName": "Fitz Elliott"}}, "lastModifiedDateTime": "2017-02-23T07:28:18.873Z"}], "eTag": "aRjRENTBFNDAwREZFN0Q0RSExNTQuMg", "parentReference": {"name": "onenote", "driveId": "f4d50e400dfe7d4e", "id": "F4D50E400DFE7D4E!289", "path": "/drive/root:/onenote"}, "id": "F4D50E400DFE7D4E!154", "name": "foo", "package": {"type": "oneNote"}, "@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", "size": 28359, "webUrl": "https://1drv.ms/o/s!AE59_g1ADtX0gRo", "cTag": "adDpGNEQ1MEU0MDBERkU3RDRFITE1NC42MzYyNjQ4MzE4NTQxMDAwMDA", "lastModifiedBy": {"user": {"id": "f4d50e400dfe7d4e", "displayName": "Fitz Elliott"}}, "lastModifiedDateTime": "2017-03-30T15:06:25.41Z"}, + "onenote_revisions": { + "@delta.token" : "aTE09NjM2MjY0ODMxODU0MTA7SUQ9RjRENTBFNDAwREZFN0Q0RSExNTQ7TFI9NjM2Mzg5MzczNzM0NjA7RVA9MTY7U0k9MjM7U0c9MTtTTz0yO1BJPTM", + "value" : [ + { + "package" : { + "type" : "oneNote" + }, + "parentReference" : { + "driveId" : "f4d50e400dfe7d4e", + "path" : "/drive/root:/onenote", + "id" : "F4D50E400DFE7D4E!289", + "name" : "onenote" + }, + "webUrl" : "https://1drv.ms/o/s!AE59_g1ADtX0gRo", + "size" : 28359, + "id" : "F4D50E400DFE7D4E!154", + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-03-30T15:06:25.41Z", + "createdDateTime" : "2017-02-23T07:27:44.253Z" + }, + "name" : "foo", + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSExNTQuMg", + "lastModifiedBy" : { + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "lastModifiedDateTime" : "2017-03-30T15:06:25.41Z", + "cTag" : "adDpGNEQ1MEU0MDBERkU3RDRFITE1NC42MzYyNjQ4MzE4NTQxMDAwMDA", + "createdDateTime" : "2017-02-23T07:27:44.253Z", + "createdBy" : { + "application" : { + "id" : "44048800" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + } + }, + { + "id" : "F4D50E400DFE7D4E!158", + "file" : { + "mimeType" : "application/msonenote" + }, + "@content.downloadUrl" : "https://iphwow.bn1303.livefilestore.com/y4pB5XnZJaQpc7j3QF0J1Vp1WsJ9rxxM--_MPMmLGQYIXPQ_SmmGN02qVe8bhBWVX3W6E_2ALvntfbHW7xQEgmFAWapl9pFrFZaKfJmwSd93nXlpRazVF3M-qmx2wWZN44gQiAYcWyhtU8VIkl2AhOoo6BeVcX2CJuOkqAhZLeYQJZ2jUykEsCBBMfJsxCt12xQfKzAU6jF0OojOE_hkNrs6Q", + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-02-23T07:28:18.86Z", + "createdDateTime" : "2017-02-23T07:28:17.623Z" + }, + "name" : "Open Notebook.onetoc2", + "webUrl" : "https://1drv.ms/o/s!AE59_g1ADtX0gR4", + "parentReference" : { + "name" : "foo", + "path" : "/drive/root:/onenote/foo", + "id" : "F4D50E400DFE7D4E!154", + "driveId" : "f4d50e400dfe7d4e" + }, + "size" : 2885, + "createdDateTime" : "2017-02-23T07:28:17.623Z", + "createdBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + } + }, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSExNTguNA", + "lastModifiedBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + } + }, + "lastModifiedDateTime" : "2017-02-23T07:28:18.873Z" + }, + { + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-02-23T07:28:30.647Z", + "createdDateTime" : "2017-02-23T07:28:14.373Z" + }, + "name" : "foo.one", + "id" : "F4D50E400DFE7D4E!157", + "file" : { + "mimeType" : "application/msonenote" + }, + "@content.downloadUrl" : "https://iphwow.bn1303.livefilestore.com/y4puP-eaKKsYhT-SUFQiQJWEAD7iZQ2_Gkp_Er8l7zsho8jmKqG7Qhm5UT0ndG0kr0uhB_yt7_7VdjwLmUNAzoviWWnORHJ8FdK0oEmwgKPB_jvAxPV7ozee5fGFX2JWlBsenzklMLAMuDRHDLKWAKGOogg2m5H5UBIzqkH1tC8B6XM-xLGPlGh7TQGdV99bmRbCjGSI2CI-vbeRXoutoORbw", + "webUrl" : "https://1drv.ms/o/s!AE59_g1ADtX0gR0", + "parentReference" : { + "path" : "/drive/root:/onenote/foo", + "id" : "F4D50E400DFE7D4E!154", + "name" : "foo", + "driveId" : "f4d50e400dfe7d4e" + }, + "size" : 25474, + "createdBy" : { + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "createdDateTime" : "2017-02-23T07:28:14.373Z", + "lastModifiedDateTime" : "2017-02-23T07:28:30.663Z", + "lastModifiedBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + } + }, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSExNTcuMjI" + } + ], + "@odata.deltaLink" : "https://api.onedrive.com/v1.0/drives('me')/items('F4D50E400DFE7D4E!154')/view.delta?$top=250&token=aTE09NjM2MjY0ODMxODU0MTA7SUQ9RjRENTBFNDAwREZFN0Q0RSExNTQ7TFI9NjM2Mzg5MzczNzM0NjA7RVA9MTY7U0k9MjM7U0c9MTtTTz0yO1BJPTM", + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items" + } +} diff --git a/tests/providers/onedrive/fixtures/paths.json b/tests/providers/onedrive/fixtures/paths.json new file mode 100644 index 000000000..5f2a7d133 --- /dev/null +++ b/tests/providers/onedrive/fixtures/paths.json @@ -0,0 +1,53 @@ +{ + "deeply_nested_file_id": "F4D50E400DFE7D4E!299", + "deeply_nested_file_metadata": { + "id" : "F4D50E400DFE7D4E!299", + "createdBy" : { + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-08-21T20:48:50.636Z", + "createdDateTime" : "2017-08-21T20:48:38.633Z" + }, + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21299')/children", + "file" : { + "mimeType" : "text/plain", + "hashes" : { + "sha1Hash" : "780892A03B54C6221AAD649266F85111EB707CE0" + } + }, + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTkuMg", + "createdDateTime" : "2017-08-21T20:48:38.633Z", + "lastModifiedDateTime" : "2017-08-21T20:48:50.653Z", + "name" : "the kraken.txt", + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5OS4yNTg", + "lastModifiedBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + }, + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + } + }, + "parentReference" : { + "path" : "/drive/root:/deep/deeper/deepest/positively%20abyssyal", + "id" : "F4D50E400DFE7D4E!298", + "name" : "positively abyssyal", + "driveId" : "f4d50e400dfe7d4e" + }, + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0gis", + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4mjtosOcbGBtNxYmp5-H-TYeT7TdFxv_EMev3KEQcOqMc_a6mKODjh0SOew6vLNy12kUopxRF3kihUHYY18zanCHKN3N7Wb28ppZmFGjQ4kA2RVrk7f2QqDpbA5VAxZ4zmqQcsl5aVYEH9_pCBwhhcJzTF-VQSZELL5dG4RdLPqQX4tUo8KEyonlWb0-uuR_7ssfZXTIgu5fIqGvskT882y2H3zOF8P6tSJAVVpvKuOxnXAIITn1ghKu1KlDpzktcPXmDkjY2sE_vRBn_5CM0zzg", + "children" : [], + "size" : 33 + } +} diff --git a/tests/providers/onedrive/fixtures/revisions.json b/tests/providers/onedrive/fixtures/revisions.json new file mode 100644 index 000000000..e71b6eae2 --- /dev/null +++ b/tests/providers/onedrive/fixtures/revisions.json @@ -0,0 +1,56 @@ +{ + "root_id": "F4D50E400DFE7D4E!103", + "file_id": "F4D50E400DFE7D4E!291", + "file_revisions": { + "value" : [ + { + "@content.downloadUrl" : "https://kdqyaa.bn1303.livefilestore.com/y4pIg3zgkmcBaQ_b2CpkxiYLihuF-GaqI-zBWermrthafHBogxMCjK6Q2qA_DoELVL0-oogK2WBYfx_CKyjynFBkRe61e6OsMDAfn0NEo4fSLSXamfrMRZ0-Pyf8ZgUujCNHpaihbkj2hwIlvnNJez0ZDerAEdfA7jos7JQnVfEAU2GNXGnsyx9Yrn9VC72xLmeMdDh676UTL9gpG-2xj4BX1AI2Ro7phbbB1n2kwnwNRZOaK1tusL8cyUfjs7joD4X", + "createdDateTime" : "2017-08-17T17:49:39.613Z", + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-08-17T17:49:50.363Z", + "createdDateTime" : "2017-08-17T17:49:39.613Z" + }, + "name" : "toes.txt", + "size" : 11, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTEuMg", + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5MS4yNTg", + "createdBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + }, + "application" : { + "id" : "481710a4" + } + }, + "lastModifiedDateTime" : "2017-08-17T17:49:50.38Z", + "file" : { + "mimeType" : "text/plain", + "hashes" : { + "sha1Hash" : "D6FAC576DCF80198874C9C9476F021AF3F12688C" + } + }, + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giM", + "id" : "F4D50E400DFE7D4E!291", + "parentReference" : { + "id" : "F4D50E400DFE7D4E!103", + "name" : "root:", + "path" : "/drive/root:", + "driveId" : "f4d50e400dfe7d4e" + }, + "lastModifiedBy" : { + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + }, + "application" : { + "id" : "481710a4" + } + } + } + ], + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items", + "@odata.deltaLink" : "https://api.onedrive.com/v1.0/drives('me')/items('F4D50E400DFE7D4E!291')/view.delta?$top=250&token=aTE09NjM2Mzg1OTM3MTAzMzc7SUQ9RjRENTBFNDAwREZFN0Q0RSEyOTE7TFI9NjM2Mzg5MTk0MDUyMTA7RVA9MTY7U0k9ODA7U0c9MTtTTz0yO1BJPTM", + "@delta.token" : "aTE09NjM2Mzg1OTM3MTAzMzc7SUQ9RjRENTBFNDAwREZFN0Q0RSEyOTE7TFI9NjM2Mzg5MTk0MDUyMTA7RVA9MTY7U0k9ODA7U0c9MTtTTz0yO1BJPTM" + } +} diff --git a/tests/providers/onedrive/fixtures/root_provider.json b/tests/providers/onedrive/fixtures/root_provider.json new file mode 100644 index 000000000..780cb2786 --- /dev/null +++ b/tests/providers/onedrive/fixtures/root_provider.json @@ -0,0 +1,342 @@ +{ + "file_id": "F4D50E400DFE7D4E!291", + "folder_id": "F4D50E400DFE7D4E!290", + "subfile_id": "F4D50E400DFE7D4E!292", + "subfile_metadata" : { + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21292')/children", + "lastModifiedDateTime" : "2017-08-17T17:53:21.13Z", + "createdDateTime" : "2017-08-17T17:50:26.877Z", + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4mSGfK7V7xQl1tZrz7UQTsCKyTXFp0d0H75AQdzM3_GgwUoPdE4-RkkZF3-c14dlj-oPoiCttFqlkMe2Ja-R-rzrcjUKNbl5HaprTkANJe_Nb8Qv0EOJ-ngFTg6-NaIx7tgvPXrBvaDMyyMWfUSuDnfNQVumL9xmQ-CbcpJe-N86sACf1RERKPsAsesO2VWSmevXUK6U1A87Efw1bk3FAGp7bh5RSmZfvpXSGLswPRbmI_xShjMdo8J1DUsTnC6remAtRuUXyZ5yEpJCpiY75kqw", + "createdBy" : { + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "lastModifiedBy" : { + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTIuMg", + "size" : 40, + "id" : "F4D50E400DFE7D4E!292", + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giQ", + "children" : [], + "name" : "bicuspid.txt", + "file" : { + "mimeType" : "text/plain", + "hashes" : { + "sha1Hash" : "A61275F93B2710487CE2C94F9CC256E413EA6871" + } + }, + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5Mi4yNTg", + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-08-17T17:53:21.113Z", + "createdDateTime" : "2017-08-17T17:50:26.876Z" + }, + "parentReference" : { + "id" : "F4D50E400DFE7D4E!290", + "driveId" : "f4d50e400dfe7d4e", + "path" : "/drive/root:/teeth", + "name" : "teeth" + } + }, + "folder_metadata" : { + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21290')/children", + "lastModifiedDateTime" : "2017-08-17T17:53:21.13Z", + "createdDateTime" : "2017-08-17T17:49:26.74Z", + "id" : "F4D50E400DFE7D4E!290", + "lastModifiedBy" : { + "application" : { + "displayName" : "OneDrive website", + "id" : "44048800" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "createdBy" : { + "application" : { + "displayName" : "OneDrive website", + "id" : "44048800" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "size" : 40, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTAuMA", + "webUrl" : "https://1drv.ms/f/s!AE59_g1ADtX0giI", + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "name" : "teeth", + "children" : [ + { + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4mBcVOkGovSm5roxVUpCkHMruB1kdSjruzCEgbQ846jlXD6ZDSYLy96yPW5LSXKf6p8C6Qn_-QqXdvXfM9KkPFH7L9BNyaMbSaN6aU_AC2NxQTSL-0VD-9xNA1riqWip9e2rB0FL_iSy7hkGfar_pem_K3bLAcjUEm4tvNMTEEetLvBYbr60LScwGKyky8z-eRt-hsdZNRRaF-hbrcR9j7ZbDquEuFRFk0mX1psLvqU5mseERpL22j3V1UNt-4_CCEwxc4bUmpMIKm8_phvSa2rA", + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5Mi4yNTg", + "file" : { + "mimeType" : "text/plain", + "hashes" : { + "sha1Hash" : "A61275F93B2710487CE2C94F9CC256E413EA6871" + } + }, + "id" : "F4D50E400DFE7D4E!292", + "lastModifiedBy" : { + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + }, + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + } + }, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTIuMg", + "createdBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + }, + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + } + }, + "size" : 40, + "fileSystemInfo" : { + "createdDateTime" : "2017-08-17T17:50:26.876Z", + "lastModifiedDateTime" : "2017-08-17T17:53:21.113Z" + }, + "parentReference" : { + "id" : "F4D50E400DFE7D4E!290", + "path" : "/drive/root:/teeth", + "driveId" : "f4d50e400dfe7d4e", + "name" : "teeth" + }, + "lastModifiedDateTime" : "2017-08-17T17:53:21.13Z", + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giQ", + "name" : "bicuspid.txt", + "createdDateTime" : "2017-08-17T17:50:26.877Z" + } + ], + "cTag" : "adDpGNEQ1MEU0MDBERkU3RDRFITI5MC42MzYzODU4OTIwMTEzMDAwMDA", + "parentReference" : { + "path" : "/drive/root:", + "driveId" : "f4d50e400dfe7d4e", + "id" : "F4D50E400DFE7D4E!103" + }, + "folder" : { + "folderView" : { + "viewType" : "thumbnails", + "sortBy" : "name", + "sortOrder" : "ascending" + }, + "childCount" : 1 + }, + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-08-17T17:49:26.74Z", + "createdDateTime" : "2017-08-17T17:49:26.74Z" + } + }, + "root_metadata" : { + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21103')/children", + "lastModifiedDateTime" : "2017-08-17T17:53:21.13Z", + "createdDateTime" : "2015-12-17T19:56:08.533Z", + "createdBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + }, + "application" : { + "displayName" : "OneDrive website", + "id" : "44048800" + } + }, + "size" : 8370382, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSExMDMuMA", + "lastModifiedBy" : { + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + }, + "application" : { + "displayName" : "OneDrive website", + "id" : "44048800" + } + }, + "id" : "F4D50E400DFE7D4E!103", + "webUrl" : "https://onedrive.live.com/?cid=f4d50e400dfe7d4e", + "root" : {}, + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "children" : [ + { + "name" : "teeth", + "createdDateTime" : "2017-08-17T17:49:26.74Z", + "webUrl" : "https://1drv.ms/f/s!AE59_g1ADtX0giI", + "lastModifiedDateTime" : "2017-08-17T17:53:21.13Z", + "folder" : { + "childCount" : 1, + "folderView" : { + "sortOrder" : "ascending", + "viewType" : "thumbnails", + "sortBy" : "name" + } + }, + "fileSystemInfo" : { + "createdDateTime" : "2017-08-17T17:49:26.74Z", + "lastModifiedDateTime" : "2017-08-17T17:49:26.74Z" + }, + "parentReference" : { + "path" : "/drive/root:", + "driveId" : "f4d50e400dfe7d4e", + "id" : "F4D50E400DFE7D4E!103" + }, + "cTag" : "adDpGNEQ1MEU0MDBERkU3RDRFITI5MC42MzYzODU4OTIwMTEzMDAwMDA", + "lastModifiedBy" : { + "application" : { + "displayName" : "OneDrive website", + "id" : "44048800" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "createdBy" : { + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + }, + "application" : { + "displayName" : "OneDrive website", + "id" : "44048800" + } + }, + "size" : 40, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTAuMA", + "id" : "F4D50E400DFE7D4E!290" + }, + { + "parentReference" : { + "id" : "F4D50E400DFE7D4E!103", + "path" : "/drive/root:", + "driveId" : "f4d50e400dfe7d4e" + }, + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-08-17T17:49:50.363Z", + "createdDateTime" : "2017-08-17T17:49:39.613Z" + }, + "id" : "F4D50E400DFE7D4E!291", + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTEuMg", + "lastModifiedBy" : { + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + }, + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + } + }, + "createdBy" : { + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "size" : 11, + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4meLf1e64kTeaaAjfKRGKi7kSXEGParV630lHnM-0ncCwon4O31FHpZOI69JmlR8Oj6guuE4frl2izAZ4zwEQVir61orFhQi8bg1OHs0pbg1PChGfXEpX6EFy6Qk1jkhezodbGbkGvw9D0FGmuGVSMFkvymvLIDn6H8cqFsCIpkkb6XD5V7eEyGfHVfn4DLogRgir9ys2t87swgfzSJsSu4WpKfHCQdZ_V3cyrcK5ta8madQAvSlIlZuHaBwBn6H_PgDQW9xKo9txcUnwRMMoJeA", + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5MS4yNTg", + "file" : { + "hashes" : { + "sha1Hash" : "D6FAC576DCF80198874C9C9476F021AF3F12688C" + }, + "mimeType" : "text/plain" + }, + "createdDateTime" : "2017-08-17T17:49:39.613Z", + "name" : "toes.txt", + "lastModifiedDateTime" : "2017-08-17T17:49:50.38Z", + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giM" + } + ], + "name" : "root", + "cTag" : "adDpGNEQ1MEU0MDBERkU3RDRFITEwMy42MzYzODU4OTIwMTEzMDAwMDA", + "folder" : { + "childCount" : 27, + "folderView" : { + "sortBy" : "name", + "viewType" : "thumbnails", + "sortOrder" : "ascending" + } + }, + "fileSystemInfo" : { + "lastModifiedDateTime" : "2015-12-17T19:56:08.533Z", + "createdDateTime" : "2015-12-17T19:56:08.533Z" + } + }, + "file_metadata" : { + "fileSystemInfo" : { + "createdDateTime" : "2017-08-17T17:49:39.613Z", + "lastModifiedDateTime" : "2017-08-17T17:49:50.363Z" + }, + "parentReference" : { + "id" : "F4D50E400DFE7D4E!103", + "path" : "/drive/root:", + "driveId" : "f4d50e400dfe7d4e" + }, + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5MS4yNTg", + "file" : { + "hashes" : { + "sha1Hash" : "D6FAC576DCF80198874C9C9476F021AF3F12688C" + }, + "mimeType" : "text/plain" + }, + "name" : "toes.txt", + "children" : [], + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giM", + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4mB8JhDUWbofzVglNap3rO5i6R7jOQyJAz995dPlkrOiQeOV2jgK-EOf916z8YHi9A42WCTMVfNmHjJliYLccUFzJgsEK3j3cviT2YLlZBMRVN-sC0mfvZz_ZeDgiLzfSChMmNXkRoq6Ymh_F8r8jRAvZTzJOgyX3F7jdw4qcY27tz95Rutrl68W0Z8ntuh3bVoPIDHC5kckF8sSWoyv5j4BfRQCckjyrmaV8F1BM5Cb1x10WNdE7CP_X1bBFqY7ZTJzYcsQcDR07BdalvRTDp-A", + "id" : "F4D50E400DFE7D4E!291", + "size" : 11, + "createdBy" : { + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + }, + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + } + }, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTEuMg", + "lastModifiedBy" : { + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "createdDateTime" : "2017-08-17T17:49:39.613Z", + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21291')/children", + "lastModifiedDateTime" : "2017-08-17T17:49:50.38Z" + } +} diff --git a/tests/providers/onedrive/fixtures/subfolder_provider.json b/tests/providers/onedrive/fixtures/subfolder_provider.json new file mode 100644 index 000000000..5c5487e15 --- /dev/null +++ b/tests/providers/onedrive/fixtures/subfolder_provider.json @@ -0,0 +1,401 @@ +{ + "root_id" : "F4D50E400DFE7D4E!290", + "file_id" : "F4D50E400DFE7D4E!292", + "folder_id" : "F4D50E400DFE7D4E!293", + "subfile_id" : "F4D50E400DFE7D4E!294", + "outside_file_id" : "F4D50E400DFE7D4E!291", + "root_metadata": { + "name" : "teeth", + "createdBy" : { + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + }, + "application" : { + "id" : "44048800", + "displayName" : "OneDrive website" + } + }, + "parentReference" : { + "path" : "/drive/root:", + "id" : "F4D50E400DFE7D4E!103", + "driveId" : "f4d50e400dfe7d4e" + }, + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21290')/children", + "lastModifiedBy" : { + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + }, + "application" : { + "id" : "44048800", + "displayName" : "OneDrive website" + } + }, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTAuMA", + "folder" : { + "childCount" : 2, + "folderView" : { + "viewType" : "thumbnails", + "sortBy" : "name", + "sortOrder" : "ascending" + } + }, + "createdDateTime" : "2017-08-17T17:49:26.74Z", + "id" : "F4D50E400DFE7D4E!290", + "fileSystemInfo" : { + "createdDateTime" : "2017-08-17T17:49:26.74Z", + "lastModifiedDateTime" : "2017-08-17T17:49:26.74Z" + }, + "webUrl" : "https://1drv.ms/f/s!AE59_g1ADtX0giI", + "children" : [ + { + "createdDateTime" : "2017-08-17T19:07:47.843Z", + "name" : "crushers", + "id" : "F4D50E400DFE7D4E!293", + "createdBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + }, + "application" : { + "id" : "44048800", + "displayName" : "OneDrive website" + } + }, + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-08-17T19:07:47.843Z", + "createdDateTime" : "2017-08-17T19:07:47.843Z" + }, + "parentReference" : { + "driveId" : "f4d50e400dfe7d4e", + "path" : "/drive/root:/teeth", + "id" : "F4D50E400DFE7D4E!290", + "name" : "teeth" + }, + "webUrl" : "https://1drv.ms/f/s!AE59_g1ADtX0giU", + "lastModifiedDateTime" : "2017-08-17T19:08:30.337Z", + "cTag" : "adDpGNEQ1MEU0MDBERkU3RDRFITI5My42MzYzODU5MzcxMDMzNzAwMDA", + "lastModifiedBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + }, + "application" : { + "displayName" : "OneDrive website", + "id" : "44048800" + } + }, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTMuMA", + "folder" : { + "childCount" : 1, + "folderView" : { + "sortBy" : "name", + "sortOrder" : "ascending", + "viewType" : "thumbnails" + } + }, + "size" : 45 + }, + { + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4m6vJbXfO-fuQZKr11RiBjuRAzM_DcRzU3JtT06HHt17abJkMJBCTuyBDXr18CPjaLPVVB8F02VGPXi2kmJYy4OdCjrIZ72J3FlpKTbSDd-ccQwvXzZNoVZWhLDU5_bx-aR588pIUcxhBMN527kR99PnDgGEhu6nt8xMNF0y4urvxBoT-snnzNfAnqqqlnYmIVHd7CEFihZigJVdKd_Q2BPlNuiVSpGjnHgYyuXtetOEDOfPJiTCZ80drcFr5z1d_ech-sPVbjtgTtdpTm8ZfjEg", + "parentReference" : { + "driveId" : "f4d50e400dfe7d4e", + "path" : "/drive/root:/teeth", + "id" : "F4D50E400DFE7D4E!290", + "name" : "teeth" + }, + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giQ", + "createdBy" : { + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + }, + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + } + }, + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-08-17T17:53:21.113Z", + "createdDateTime" : "2017-08-17T17:50:26.876Z" + }, + "createdDateTime" : "2017-08-17T17:50:26.877Z", + "name" : "bicuspid.txt", + "id" : "F4D50E400DFE7D4E!292", + "size" : 40, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTIuMg", + "file" : { + "mimeType" : "text/plain", + "hashes" : { + "sha1Hash" : "A61275F93B2710487CE2C94F9CC256E413EA6871" + } + }, + "lastModifiedBy" : { + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "lastModifiedDateTime" : "2017-08-17T17:53:21.13Z", + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5Mi4yNTg" + } + ], + "cTag" : "adDpGNEQ1MEU0MDBERkU3RDRFITI5MC42MzYzODU5MzcxMDMzNzAwMDA", + "lastModifiedDateTime" : "2017-08-17T19:08:30.337Z", + "size" : 85 + }, + "file_metadata" : { + "createdBy" : { + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giQ", + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21292')/children", + "size" : 40, + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4mSGfK7V7xQl1tZrz7UQTsCKyTXFp0d0H75AQdzM3_GgwUoPdE4-RkkZF3-c14dlj-oPoiCttFqlkMe2Ja-R-rzrcjUKNbl5HaprTkANJe_Nb8Qv0EOJ-ngFTg6-NaIx7tgvPXrBvaDMyyMWfUSuDnfNQVumL9xmQ-CbcpJe-N86sACf1RERKPsAsesO2VWSmevXUK6U1A87Efw1bk3FAGp7bh5RSmZfvpXSGLswPRbmI_xShjMdo8J1DUsTnC6remAtRuUXyZ5yEpJCpiY75kqw", + "file" : { + "mimeType" : "text/plain", + "hashes" : { + "sha1Hash" : "A61275F93B2710487CE2C94F9CC256E413EA6871" + } + }, + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5Mi4yNTg", + "parentReference" : { + "driveId" : "f4d50e400dfe7d4e", + "path" : "/drive/root:/teeth", + "name" : "teeth", + "id" : "F4D50E400DFE7D4E!290" + }, + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "lastModifiedDateTime" : "2017-08-17T17:53:21.13Z", + "name" : "bicuspid.txt", + "lastModifiedBy" : { + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + }, + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + } + }, + "fileSystemInfo" : { + "createdDateTime" : "2017-08-17T17:50:26.876Z", + "lastModifiedDateTime" : "2017-08-17T17:53:21.113Z" + }, + "id" : "F4D50E400DFE7D4E!292", + "children" : [], + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTIuMg", + "createdDateTime" : "2017-08-17T17:50:26.877Z" + }, + "folder_metadata" : { + "cTag" : "adDpGNEQ1MEU0MDBERkU3RDRFITI5My42MzYzODU5MzcxMDMzNzAwMDA", + "createdBy" : { + "application" : { + "id" : "44048800", + "displayName" : "OneDrive website" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "webUrl" : "https://1drv.ms/f/s!AE59_g1ADtX0giU", + "size" : 45, + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21293')/children", + "name" : "crushers", + "parentReference" : { + "id" : "F4D50E400DFE7D4E!290", + "name" : "teeth", + "driveId" : "f4d50e400dfe7d4e", + "path" : "/drive/root:/teeth" + }, + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "lastModifiedDateTime" : "2017-08-17T19:08:30.337Z", + "id" : "F4D50E400DFE7D4E!293", + "children" : [ + { + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTQuMg", + "lastModifiedDateTime" : "2017-08-17T19:08:30.337Z", + "parentReference" : { + "id" : "F4D50E400DFE7D4E!293", + "name" : "crushers", + "driveId" : "f4d50e400dfe7d4e", + "path" : "/drive/root:/teeth/crushers" + }, + "name" : "molars.txt", + "createdDateTime" : "2017-08-17T19:08:03.03Z", + "fileSystemInfo" : { + "createdDateTime" : "2017-08-17T19:08:03.03Z", + "lastModifiedDateTime" : "2017-08-17T19:08:30.32Z" + }, + "size" : 45, + "createdBy" : { + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + }, + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + } + }, + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giY", + "lastModifiedBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + }, + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + } + }, + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4mO1VUyGkx1jMbvuSh39sB5hHkVtY9jF-z0eEzmmSwz85fwmzQwFA0MKYlJiTFHPuUItQaHbocR-fxLCt4-LAM--20CvEyr4hX6BNtnlhxwg3FRFg_fLYsk8ESqqJbbnxQYLmBpqJZ1ympsnv1U9KxWoW0Fb07HqKxm5bKIWhFmmmFwfSoTe80yE_-9eV9-ATFAuBFkZ8uVODyrgIN-hsrKDWh4CUJs2V3FFEg_6nERUpncQWfsL7HaMJbUD4LsU_tpSLEemayqxcguyUmMdrCmA", + "file" : { + "mimeType" : "text/plain", + "hashes" : { + "sha1Hash" : "71E36DE69F693D7DED11A689CEE9779DCCC37928" + } + }, + "id" : "F4D50E400DFE7D4E!294", + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5NC4yNTg" + } + ], + "lastModifiedBy" : { + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + }, + "application" : { + "displayName" : "OneDrive website", + "id" : "44048800" + } + }, + "fileSystemInfo" : { + "createdDateTime" : "2017-08-17T19:07:47.843Z", + "lastModifiedDateTime" : "2017-08-17T19:07:47.843Z" + }, + "createdDateTime" : "2017-08-17T19:07:47.843Z", + "folder" : { + "folderView" : { + "sortBy" : "name", + "sortOrder" : "ascending", + "viewType" : "thumbnails" + }, + "childCount" : 1 + }, + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTMuMA" + }, + "subfile_metadata" : { + "createdBy" : { + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + }, + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + } + }, + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giY", + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21294')/children", + "size" : 45, + "file" : { + "mimeType" : "text/plain", + "hashes" : { + "sha1Hash" : "71E36DE69F693D7DED11A689CEE9779DCCC37928" + } + }, + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4msgC4teoghHKiACyXiJoRwNNI0vUf_rsaT6tci9H0d_YRIL7l-z32DkOKQAySqmeNQ2p5-G0thk3CjRmRVllqhVyM7xrHLO_1ybnfNO1IN_31WdLytIjcXUb_G9068o2wClIinh-liGSz3dsm7cGb6I0cOxYwFy8GgqN4X2QfF4VErDCWy7ZCNoUiYh4jlew6ZI9W0-_Xkv09NvToYE4ErnniEnh-_trg1MrtOlD7bUqxBleJHr6h4zeqSyk5M1gC-HUfwKpFGdOq4yEsf36MKQ", + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5NC4yNTg", + "parentReference" : { + "path" : "/drive/root:/teeth/crushers", + "driveId" : "f4d50e400dfe7d4e", + "name" : "crushers", + "id" : "F4D50E400DFE7D4E!293" + }, + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "lastModifiedDateTime" : "2017-08-17T19:08:30.337Z", + "name" : "molars.txt", + "lastModifiedBy" : { + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "fileSystemInfo" : { + "createdDateTime" : "2017-08-17T19:08:03.03Z", + "lastModifiedDateTime" : "2017-08-17T19:08:30.32Z" + }, + "id" : "F4D50E400DFE7D4E!294", + "children" : [], + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTQuMg", + "createdDateTime" : "2017-08-17T19:08:03.03Z" + }, + "outside_file_metadata" : { + "createdDateTime" : "2017-08-17T17:49:39.613Z", + "eTag" : "aRjRENTBFNDAwREZFN0Q0RSEyOTEuMg", + "id" : "F4D50E400DFE7D4E!291", + "children" : [], + "lastModifiedBy" : { + "application" : { + "displayName" : "OneDrive", + "id" : "481710a4" + }, + "user" : { + "id" : "f4d50e400dfe7d4e", + "displayName" : "Fitz Elliott" + } + }, + "fileSystemInfo" : { + "lastModifiedDateTime" : "2017-08-17T17:49:50.363Z", + "createdDateTime" : "2017-08-17T17:49:39.613Z" + }, + "name" : "toes.txt", + "parentReference" : { + "driveId" : "f4d50e400dfe7d4e", + "path" : "/drive/root:", + "id" : "F4D50E400DFE7D4E!103" + }, + "@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", + "lastModifiedDateTime" : "2017-08-17T17:49:50.38Z", + "file" : { + "hashes" : { + "sha1Hash" : "D6FAC576DCF80198874C9C9476F021AF3F12688C" + }, + "mimeType" : "text/plain" + }, + "@content.downloadUrl" : "https://public.bn1303.livefilestore.com/y4mB8JhDUWbofzVglNap3rO5i6R7jOQyJAz995dPlkrOiQeOV2jgK-EOf916z8YHi9A42WCTMVfNmHjJliYLccUFzJgsEK3j3cviT2YLlZBMRVN-sC0mfvZz_ZeDgiLzfSChMmNXkRoq6Ymh_F8r8jRAvZTzJOgyX3F7jdw4qcY27tz95Rutrl68W0Z8ntuh3bVoPIDHC5kckF8sSWoyv5j4BfRQCckjyrmaV8F1BM5Cb1x10WNdE7CP_X1bBFqY7ZTJzYcsQcDR07BdalvRTDp-A", + "cTag" : "aYzpGNEQ1MEU0MDBERkU3RDRFITI5MS4yNTg", + "createdBy" : { + "application" : { + "id" : "481710a4", + "displayName" : "OneDrive" + }, + "user" : { + "displayName" : "Fitz Elliott", + "id" : "f4d50e400dfe7d4e" + } + }, + "webUrl" : "https://1drv.ms/t/s!AE59_g1ADtX0giM", + "children@odata.context" : "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('F4D50E400DFE7D4E%21291')/children", + "size" : 11 + } +} diff --git a/tests/providers/onedrive/test_metadata.py b/tests/providers/onedrive/test_metadata.py new file mode 100644 index 000000000..4f2f9dbd0 --- /dev/null +++ b/tests/providers/onedrive/test_metadata.py @@ -0,0 +1,77 @@ +import pytest + +from waterbutler.providers.onedrive.path import OneDrivePath +from waterbutler.providers.onedrive.metadata import (OneDriveFileMetadata, + OneDriveFolderMetadata, + OneDriveRevisionMetadata) + +from tests.providers.onedrive.fixtures import (revision_fixtures, + root_provider_fixtures) + + +class TestOneDriveMetadata: + + def test_build_file_metadata(self, root_provider_fixtures): + od_path = OneDrivePath.new_from_response(root_provider_fixtures['file_metadata'], 'root') + + metadata = OneDriveFileMetadata(root_provider_fixtures['file_metadata'], od_path) + assert metadata.provider == 'onedrive' + assert metadata.created_utc == '2017-08-17T17:49:39+00:00' + assert metadata.materialized_path == '/toes.txt' + assert metadata.extra == { + 'id': root_provider_fixtures['file_id'], + 'etag': 'aRjRENTBFNDAwREZFN0Q0RSEyOTEuMg', + 'webView': 'https://1drv.ms/t/s!AE59_g1ADtX0giM', + } + assert metadata.name == 'toes.txt' + assert metadata.path == '/{}'.format(root_provider_fixtures['file_id']) + assert metadata.size == 11 + assert metadata.modified == '2017-08-17T17:49:50.38Z' + assert metadata.modified_utc == '2017-08-17T17:49:50+00:00' + assert metadata.content_type == 'text/plain' + assert metadata.etag == 'aRjRENTBFNDAwREZFN0Q0RSEyOTEuMg' + + action_url = ('http://localhost:7777/v1/resources/mst3k/providers' + '/onedrive/{}'.format(root_provider_fixtures['file_id'])) + assert metadata._json_api_links('mst3k') == { + 'delete': None, + 'upload': None, + 'move': action_url, + 'download': action_url, + } + + def test_build_folder_metadata(self, root_provider_fixtures): + od_path = OneDrivePath.new_from_response(root_provider_fixtures['folder_metadata'], 'root') + + metadata = OneDriveFolderMetadata(root_provider_fixtures['folder_metadata'], od_path) + assert metadata.provider == 'onedrive' + assert metadata.name == 'teeth' + assert metadata.path == '/F4D50E400DFE7D4E!290/' + assert metadata.etag == 'aRjRENTBFNDAwREZFN0Q0RSEyOTAuMA' + assert metadata.materialized_path == '/teeth/' + assert metadata.extra == { + 'id': 'F4D50E400DFE7D4E!290', + 'etag': 'aRjRENTBFNDAwREZFN0Q0RSEyOTAuMA', + 'webView': 'https://1drv.ms/f/s!AE59_g1ADtX0giI', + 'modified_utc': '2017-08-17T17:53:21+00:00', + 'created_utc': '2017-08-17T17:49:26+00:00', + } + + assert metadata._json_api_links('mst3k') == { + 'delete': None, + 'upload': None, + 'move': ('http://localhost:7777/v1/resources/mst3k/providers' + '/onedrive/{}/'.format(root_provider_fixtures['folder_id'])), + 'new_folder': None, + } + + def test_build_revision_metadata(self, revision_fixtures): + metadata = OneDriveRevisionMetadata(revision_fixtures['file_revisions']['value'][0]) + + assert metadata.serialized() == { + 'extra': {}, + 'version': 'aRjRENTBFNDAwREZFN0Q0RSEyOTEuMg', + 'modified': '2017-08-17T17:49:50.38Z', + 'modified_utc': '2017-08-17T17:49:50+00:00', + 'versionIdentifier': 'revision', + } diff --git a/tests/providers/onedrive/test_path.py b/tests/providers/onedrive/test_path.py new file mode 100644 index 000000000..b508ffe69 --- /dev/null +++ b/tests/providers/onedrive/test_path.py @@ -0,0 +1,114 @@ +import pytest + +from waterbutler.providers.onedrive.path import OneDrivePath + +from tests.providers.onedrive.fixtures import (path_fixtures, + root_provider_fixtures, + subfolder_provider_fixtures) + + +class TestApiIdentifier: + + def test_api_identifier_none(self): + path = OneDrivePath('/foo', _ids=('root', None,)) + assert path.api_identifier is None + + def test_api_identifier_root(self): + path = OneDrivePath('/', _ids=('root',)) + assert path.api_identifier == ('root',) + + def test_api_identifier_folder_id(self): + path = OneDrivePath('/', _ids=('123456',)) + assert path.api_identifier == ('items', '123456',) + + def test_api_identifier_file_id(self): + path = OneDrivePath('/foo', _ids=('123456','7891011',)) + assert path.api_identifier == ('items', '7891011',) + + +class TestNewFromResponseRootProvider: + + def test_file_in_root(self, root_provider_fixtures): + od_path = OneDrivePath.new_from_response(root_provider_fixtures['file_metadata'], 'root') + + assert od_path.identifier == root_provider_fixtures['file_id'] + assert str(od_path) == '/toes.txt' + assert len(od_path.parts) == 2 + ids = [x.identifier for x in od_path.parts] + assert ids == ['root', root_provider_fixtures['file_id']] + + def test_folder_in_root(self, root_provider_fixtures): + od_path = OneDrivePath.new_from_response(root_provider_fixtures['folder_metadata'], 'root') + + assert od_path.identifier == root_provider_fixtures['folder_id'] + assert str(od_path) == '/teeth/' + assert len(od_path.parts) == 2 + ids = [x.identifier for x in od_path.parts] + assert ids == ['root', root_provider_fixtures['folder_id']] + + def test_file_in_subdir(self, root_provider_fixtures): + od_path = OneDrivePath.new_from_response(root_provider_fixtures['subfile_metadata'], 'root') + + assert od_path.identifier == root_provider_fixtures['subfile_id'] + assert str(od_path) == '/teeth/bicuspid.txt' + assert len(od_path.parts) == 3 + ids = [x.identifier for x in od_path.parts] + assert ids == ['root', + root_provider_fixtures['folder_id'], + root_provider_fixtures['subfile_id']] + + def test_fails_without_base_folder(self, root_provider_fixtures): + with pytest.raises(Exception): + od_path = OneDrivePath.new_from_response(root_provider_fixtures['file_metadata']) + + def test_insert_zero_ids(self, path_fixtures): + file_metadata = path_fixtures['deeply_nested_file_metadata'] + od_path = OneDrivePath.new_from_response(file_metadata, 'root') + + file_id = path_fixtures['deeply_nested_file_id'] + assert od_path.identifier == file_id + assert str(od_path) == '/deep/deeper/deepest/positively abyssyal/the kraken.txt' + assert len(od_path.parts) == 6 + ids = [x.identifier for x in od_path.parts] + assert ids == ['root', None, None, None, 'F4D50E400DFE7D4E!298', file_id] + + + +class TestNewFromResponseSubfolderProvider: + + def test_file_in_root(self, subfolder_provider_fixtures): + od_path = OneDrivePath.new_from_response(subfolder_provider_fixtures['file_metadata'], + subfolder_provider_fixtures['root_id']) + + assert od_path.identifier == subfolder_provider_fixtures['file_id'] + assert str(od_path) == '/bicuspid.txt' + assert len(od_path.parts) == 2 + ids = [x.identifier for x in od_path.parts] + assert ids == [subfolder_provider_fixtures['root_id'], + subfolder_provider_fixtures['file_id']] + + + def test_subfolder_base_is_folder(self, subfolder_provider_fixtures): + od_path = OneDrivePath.new_from_response(subfolder_provider_fixtures['folder_metadata'], + subfolder_provider_fixtures['root_id']) + + assert od_path.identifier == subfolder_provider_fixtures['folder_id'] + assert str(od_path) == '/crushers/' + assert len(od_path.parts) == 2 + ids = [x.identifier for x in od_path.parts] + assert ids == [subfolder_provider_fixtures['root_id'], + subfolder_provider_fixtures['folder_id']] + + def test_file_in_subdir(self, subfolder_provider_fixtures): + od_path = OneDrivePath.new_from_response(subfolder_provider_fixtures['subfile_metadata'], + subfolder_provider_fixtures['root_id'], + base_folder_metadata=subfolder_provider_fixtures['root_metadata']) + + assert od_path.identifier == subfolder_provider_fixtures['subfile_id'] + assert str(od_path) == '/crushers/molars.txt' + assert len(od_path.parts) == 3 + ids = [x.identifier for x in od_path.parts] + assert ids == [subfolder_provider_fixtures['root_id'], + subfolder_provider_fixtures['folder_id'], + subfolder_provider_fixtures['subfile_id']] + diff --git a/tests/providers/onedrive/test_provider.py b/tests/providers/onedrive/test_provider.py index 913fbb6e5..5d8516465 100644 --- a/tests/providers/onedrive/test_provider.py +++ b/tests/providers/onedrive/test_provider.py @@ -1,7 +1,5 @@ import io import pytest -import logging -from http import client import aiohttpretty @@ -9,13 +7,15 @@ from waterbutler.core import exceptions from waterbutler.providers.onedrive import OneDriveProvider -from waterbutler.providers.onedrive.settings import settings from waterbutler.providers.onedrive.provider import OneDrivePath from waterbutler.providers.onedrive.metadata import OneDriveFileMetadata from waterbutler.providers.onedrive.metadata import OneDriveFolderMetadata from waterbutler.providers.onedrive.metadata import OneDriveRevisionMetadata -logger = logging.getLogger(__name__) +from tests.providers.onedrive.fixtures import (download_fixtures, + revision_fixtures, + root_provider_fixtures, + subfolder_provider_fixtures) @pytest.fixture @@ -32,13 +32,31 @@ def credentials(): @pytest.fixture -def settings(): - return {'folder': '11446498'} +def subfolder_settings(subfolder_provider_fixtures): + return {'folder': subfolder_provider_fixtures['root_id']} @pytest.fixture -def provider(auth, credentials, settings): - return OneDriveProvider(auth, credentials, settings) +def subfolder_provider(auth, credentials, subfolder_settings): + """Provider root is subfolder of OneDrive account root""" + return OneDriveProvider(auth, credentials, subfolder_settings) + + +@pytest.fixture +def root_settings(): + return {'folder': 'root'} + + +@pytest.fixture +def root_provider(auth, credentials, root_settings): + """Provider root is OneDrive account root""" + return OneDriveProvider(auth, credentials, root_settings) + + +@pytest.fixture +def provider(root_provider): + """Alias""" + return root_provider @pytest.fixture @@ -55,506 +73,473 @@ def file_like(file_content): def file_stream(file_like): return streams.FileStreamReader(file_like) -@pytest.fixture -def folder_object_metadata(): - return { - "size": 119410, - "name": "sub1-b", - "folder": { - "childCount": 4 - }, - "@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", - "id": "75BFE374EBEB1211!118", - "createdDateTime": "2015-11-29T17:21:09.997Z", - "lastModifiedDateTime": "2015-12-07T16:45:28.46Z", - "parentReference": { - "driveId": "75bfe374ebeb1211", - "path": "/drive/root:/ryan-test1", - "id": "75BFE374EBEB1211!107" - }, - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!118", - "cTag": "adDo3NUJGRTM3NEVCRUIxMjExITExOC42MzU4NTEwMzUyODQ2MDAwMDA", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExMTguMw", - "children": [ - { - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!118", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExMTguMw", - "fileSystemInfo": { - "lastModifiedDateTime": "2015-12-07T16:45:28.46Z", - "createdDateTime": "2015-11-29T17:21:09.997Z" - }, - "id": "75BFE374EBEB1211!118", - "lastModifiedDateTime": "2015-12-09T01:48:52.31Z", - "size": 119410, - "createdDateTime": "2015-11-29T17:21:09.997Z", - "folder": { - "childCount": 4 - }, - "cTag": "adDo3NUJGRTM3NEVCRUIxMjExITExOC42MzU4NTIyMjUzMjMxMDAwMDA", - "name": "sub1-b", - "parentReference": { - "driveId": "75bfe374ebeb1211", - "path": "/drive/root:/ryan-test1/sub1", - "id": "75BFE374EBEB1211!107" - } - }, - { - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!143", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExNDMuMTI", - "fileSystemInfo": { - "lastModifiedDateTime": "2015-12-07T17:26:09.48Z", - "createdDateTime": "2015-12-01T16:52:33.07Z" - }, - "id": "75BFE374EBEB1211!143", - "lastModifiedDateTime": "2015-12-07T17:26:09.48Z", - "size": 0, - "createdDateTime": "2015-12-01T16:52:33.07Z", - "folder": { - "childCount": 1 - }, - "cTag": "adDo3NUJGRTM3NEVCRUIxMjExITE0My42MzU4NTEwNTk2OTQ4MDAwMDA", - "name": "sub1-z", - "parentReference": { - "driveId": "75bfe374ebeb1211", - "path": "/drive/root:/ryan-test1/sub1", - "id": "75BFE374EBEB1211!107" - } - }, - { - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!150", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExNTAuMTE", - "fileSystemInfo": { - "lastModifiedDateTime": "2015-12-08T21:51:15.593Z", - "createdDateTime": "2015-12-02T20:25:26.51Z" - }, - "id": "75BFE374EBEB1211!150", - "lastModifiedDateTime": "2015-12-08T21:51:15.593Z", - "size": 83736, - "@content.downloadUrl": "https://public-ch3302.files.1drv.com/y3mgyZqUob4fS1RGIHa8w3tl0ozOlXXiKPMmz3hxZ0KbMqyZmIOnzXL8G9fWREL01mog9XRQn2g2qExRSSFce9ixl7fOlq_yjwOxX-6F2CNzgp3-wE9oThZSrvTix8h7cMD32RHd-__uwGK6Db0ErsGuxorWJKfRlmkpJFn7b8F9ZVvsIsLOmJWVKMyxrQMfves", - "cTag": "aYzo3NUJGRTM3NEVCRUIxMjExITE1MC4yNTc", - "file": { - "mimeType": "image/jpeg", - "hashes": { - "crc32Hash": "6D98C9D5", - "sha1Hash": "68A4192BF9DEAD103D7E4EA481074745932989F4" - } - }, - "name": "elect-a.jpg", - "parentReference": { - "driveId": "75bfe374ebeb1211", - "path": "/drive/root:/ryan-test1/sub1", - "id": "75BFE374EBEB1211!107" - }, - } - ], - } -@pytest.fixture -def folder_list_metadata(): - return { +class TestRootProviderValidatePath: - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!107", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExMDcuMA", - "fileSystemInfo": { - "lastModifiedDateTime": "2015-11-22T14:33:33.57Z", - "createdDateTime": "2015-11-22T14:33:33.57Z" - }, - "children@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('75BFE374EBEB1211%21107')/children", - "id": "75BFE374EBEB1211!107", - "lastModifiedDateTime": "2015-12-11T14:45:36.6Z", - "size": 203146, - "@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", - "createdDateTime": "2015-11-22T14:33:33.57Z", - "folder": { - "childCount": 3 - }, - "cTag": "adDo3NUJGRTM3NEVCRUIxMjExITEwNy42MzU4NTQ0MTkzNjYwMDAwMDA", - "children": [ - { - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!118", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExMTguMw", - "fileSystemInfo": { - "lastModifiedDateTime": "2015-12-07T16:45:28.46Z", - "createdDateTime": "2015-11-29T17:21:09.997Z" - }, - "id": "75BFE374EBEB1211!118", - "lastModifiedDateTime": "2015-12-09T01:48:52.31Z", - "size": 119410, - "createdDateTime": "2015-11-29T17:21:09.997Z", - "folder": { - "childCount": 4 - }, - "cTag": "adDo3NUJGRTM3NEVCRUIxMjExITExOC42MzU4NTIyMjUzMjMxMDAwMDA", - "name": "sub1-b", - "parentReference": { - "driveId": "75bfe374ebeb1211", - "path": "/drive/root:/ryan-test1", - "id": "75BFE374EBEB1211!107" - } - }, - { - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!143", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExNDMuMTI", - "fileSystemInfo": { - "lastModifiedDateTime": "2015-12-07T17:26:09.48Z", - "createdDateTime": "2015-12-01T16:52:33.07Z" - }, - "id": "75BFE374EBEB1211!143", - "lastModifiedDateTime": "2015-12-07T17:26:09.48Z", - "size": 0, - "createdDateTime": "2015-12-01T16:52:33.07Z", - "folder": { - "childCount": 1 - }, - "cTag": "adDo3NUJGRTM3NEVCRUIxMjExITE0My42MzU4NTEwNTk2OTQ4MDAwMDA", - "name": "sub1-z", - "parentReference": { - "driveId": "75bfe374ebeb1211", - "path": "/drive/root:/ryan-test1", - "id": "75BFE374EBEB1211!107" - } - }, - { - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!150", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExNTAuMTE", - "fileSystemInfo": { - "lastModifiedDateTime": "2015-12-08T21:51:15.593Z", - "createdDateTime": "2015-12-02T20:25:26.51Z" - }, - "id": "75BFE374EBEB1211!150", - "lastModifiedDateTime": "2015-12-08T21:51:15.593Z", - "photo": { - "takenDateTime": "2013-04-17T14:32:26Z" - }, - "size": 83736, - "@content.downloadUrl": "https://public-ch3302.files.1drv.com/y3mgyZqUob4fS1RGIHa8w3tl0ozOlXXiKPMmz3hxZ0KbMqyZmIOnzXL8G9fWREL01mog9XRQn2g2qExRSSFce9ixl7fOlq_yjwOxX-6F2CNzgp3-wE9oThZSrvTix8h7cMD32RHd-__uwGK6Db0ErsGuxorWJKfRlmkpJFn7b8F9ZVvsIsLOmJWVKMyxrQMfves", - "cTag": "aYzo3NUJGRTM3NEVCRUIxMjExITE1MC4yNTc", - "image": { - "width": 883, - "height": 431 - }, - "file": { - "mimeType": "image/jpeg", - "hashes": { - "crc32Hash": "6D98C9D5", - "sha1Hash": "68A4192BF9DEAD103D7E4EA481074745932989F4" - } - }, - "name": "elect-a.jpg", - "parentReference": { - "driveId": "75bfe374ebeb1211", - "path": "/drive/root:/ryan-test1", - "id": "75BFE374EBEB1211!107" - }, - "createdDateTime": "2015-12-02T20:25:26.51Z" - } - ], - "name": "ryan-test1", - "parentReference": { - "driveId": "75bfe374ebeb1211", - "path": "/drive/root:", - "id": "75BFE374EBEB1211!103" - } - } + @pytest.mark.asyncio + @pytest.mark.aiohttpretty + async def test_validate_v1_path_root(self, root_provider): + try: + wb_path_v1 = await root_provider.validate_v1_path('/') + except Exception as exc: + pytest.fail(str(exc)) -@pytest.fixture -def file_root_folder_metadata(): - return { - "id": "75BFE374EBEB1211!128", - "cTag": "adDo3NUJGRTM3NEVCRUIxMjExITEyOC42MzU4NTYxODI2MDA5MzAwMDA", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExMjguMA", - "size": 998322, - "name": "hello.jpg", - "parentReference": { - "id": "75BFE374EBEB1211!103", - "path": "/drive/root:/sam", - "driveId": "75bfe374ebeb1211" - }, - "@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!128", - "file": { - "hashes": { - "crc32Hash": "6D98C9D5", - "sha1Hash": "68A4192BF9DEAD103D7E4EA481074745932989F4" - }, - "mimeType": "image/jpeg" - }, - } + wb_path_v0 = await root_provider.validate_path('/') + assert wb_path_v1 == wb_path_v0 + assert wb_path_v1.identifier == 'root' -@pytest.fixture -def folder_sub_folder_metadata(): - return { - "id": "75BFE374EBEB1211!128", - "cTag": "adDo3NUJGRTM3NEVCRUIxMjExITEyOC42MzU4NTYxODI2MDA5MzAwMDA", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExMjguMA", - "size": 998322, - "name": "hello", - "parentReference": { - "id": "75BFE374EBEB1211!103", - "path": "/drive/root:/sam/i/am", - "driveId": "75bfe374ebeb1211" - }, - "@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!128", - "folder": { - "childCount": 3 - }, - } + @pytest.mark.aiohttpretty + @pytest.mark.asyncio + async def test_validate_v1_path_file(self, root_provider, root_provider_fixtures): + file_id = root_provider_fixtures['file_id'] + file_metadata = root_provider_fixtures['file_metadata'] -@pytest.fixture -def file_sub_folder_metadata(): - return { - "id": "75BFE374EBEB1211!128", - "cTag": "adDo3NUJGRTM3NEVCRUIxMjExITEyOC42MzU4NTYxODI2MDA5MzAwMDA", - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExMjguMA", - "size": 998322, - "name": "hello.jpg", - "parentReference": { - "id": "75BFE374EBEB1211!103", - "path": "/drive/root:/sam/i/am", - "driveId": "75bfe374ebeb1211" - }, - "@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!128", - "file": { - "hashes": { - "crc32Hash": "6D98C9D5", - "sha1Hash": "68A4192BF9DEAD103D7E4EA481074745932989F4" - }, - "mimeType": "image/jpeg" - }, - } + item_url = root_provider._build_item_url(file_id) + print('item url: {}'.format(item_url)) + aiohttpretty.register_json_uri('GET', item_url, body=file_metadata, status=200) -@pytest.fixture -def file_root_parent_metadata(): - return { - "id": "75BFE374EBEB1211!150", - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!150", - "@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", - "cTag": "aYzo3NUJGRTM3NEVCRUIxMjExITE1MC4yNTc", - "children": [], - "file": { - "hashes": { - "sha1Hash": "68A4192BF9DEAD103D7E4EA481074745932989F4", - "crc32Hash": "6D98C9D5" - }, - "mimeType": "image/jpeg" - }, - "fileSystemInfo": { - "createdDateTime": "2015-12-02T20:25:26.51Z", - "lastModifiedDateTime": "2015-12-08T21:51:15.593Z" - }, - "createdDateTime": "2015-12-02T20:25:26.51Z", - "size": 83736, - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExNTAuMTE", - "name": "elect-a.jpg", - "@content.downloadUrl": "https://public-ch3302.files.1drv.com/y3mnrbLFOgJJ8JQA7Ots0pzvL0xHYJx9NQJylS6IoQqp5G2CIIG5IWCKT_ADdp035kbr3qEmz6Va5j8-NCplk4ZMG_cYipxUfhP-NNl-SjlKocwc7yDplc1qWEynHGm_lME_o98pKSxNg6sKbEphRPufHea_h7LU1XH2qkFEGOIZGHQlw_JmH9fvygq8_XY2iE-", - "children@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('75BFE374EBEB1211%21150')/children", - "parentReference": { - "id": "75BFE374EBEB1211!107", - "driveId": "75bfe374ebeb1211", - "path": "/drive/root:" - }, - "lastModifiedDateTime": "2015-12-08T21:51:15.593Z" - } + file_path = '/{}'.format(file_id) + try: + wb_path_v1 = await root_provider.validate_v1_path(file_path) + except Exception as exc: + pytest.fail(str(exc)) -@pytest.fixture -def file_metadata(): - return { - "id": "75BFE374EBEB1211!150", - "webUrl": "https://onedrive.live.com/redir?resid=75BFE374EBEB1211!150", - "@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items/$entity", - "cTag": "aYzo3NUJGRTM3NEVCRUIxMjExITE1MC4yNTc", - "children": [], - "image": { - "width": 883, - "height": 431 - }, - "file": { - "hashes": { - "sha1Hash": "68A4192BF9DEAD103D7E4EA481074745932989F4", - "crc32Hash": "6D98C9D5" - }, - "mimeType": "image/jpeg" - }, - "fileSystemInfo": { - "createdDateTime": "2015-12-02T20:25:26.51Z", - "lastModifiedDateTime": "2015-12-08T21:51:15.593Z" - }, - "createdDateTime": "2015-12-02T20:25:26.51Z", - "size": 83736, - "photo": { - "takenDateTime": "2013-04-17T14:32:26Z" - }, - "eTag": "aNzVCRkUzNzRFQkVCMTIxMSExNTAuMTE", - "name": "elect-a.jpg", - "@content.downloadUrl": "https://public-ch3302.files.1drv.com/y3mnrbLFOgJJ8JQA7Ots0pzvL0xHYJx9NQJylS6IoQqp5G2CIIG5IWCKT_ADdp035kbr3qEmz6Va5j8-NCplk4ZMG_cYipxUfhP-NNl-SjlKocwc7yDplc1qWEynHGm_lME_o98pKSxNg6sKbEphRPufHea_h7LU1XH2qkFEGOIZGHQlw_JmH9fvygq8_XY2iE-", - "children@odata.context": "https://api.onedrive.com/v1.0/$metadata#drives('me')/items('75BFE374EBEB1211%21150')/children", - "parentReference": { - "id": "75BFE374EBEB1211!107", - "driveId": "75bfe374ebeb1211", - "path": "/drive/root:/ryan-test1" - }, - "lastModifiedDateTime": "2015-12-08T21:51:15.593Z" - } + file_name = '/{}'.format(file_metadata['name']) + assert str(wb_path_v1) == file_name + assert wb_path_v1.identifier == file_id + wb_path_v0 = await root_provider.validate_path(file_path) + assert str(wb_path_v0) == file_name -@pytest.fixture -def revisions_list_metadata(): - return { - } + assert wb_path_v1 == wb_path_v0 + + with pytest.raises(exceptions.NotFoundError) as exc: + await root_provider.validate_v1_path(file_path + '/') + + @pytest.mark.aiohttpretty + @pytest.mark.asyncio + async def test_validate_v1_path_folder(self, root_provider, root_provider_fixtures): + folder_id = root_provider_fixtures['folder_id'] + folder_metadata = root_provider_fixtures['folder_metadata'] + + item_url = root_provider._build_item_url(folder_id) + print('item url: {}'.format(item_url)) + aiohttpretty.register_json_uri('GET', item_url, body=folder_metadata, status=200) + + folder_path = '/{}/'.format(folder_id) + folder_name = '/{}/'.format(folder_metadata['name']) + try: + wb_path_v1 = await root_provider.validate_v1_path(folder_path) + except Exception as exc: + pytest.fail(str(exc)) + + assert str(wb_path_v1) == folder_name + assert wb_path_v1.identifier == folder_id + + wb_path_v0 = await root_provider.validate_path(folder_path) + assert str(wb_path_v0) == folder_name + + assert wb_path_v1 == wb_path_v0 + + with pytest.raises(exceptions.NotFoundError) as exc: + await root_provider.validate_v1_path(folder_path.rstrip('/')) + + +class TestSubfolderProviderValidatePath: + @pytest.mark.asyncio + @pytest.mark.aiohttpretty + async def test_validate_v1_path_root(self, subfolder_provider, subfolder_provider_fixtures): + try: + wb_path_v1 = await subfolder_provider.validate_v1_path('/') + except Exception as exc: + pytest.fail(str(exc)) -class TestValidatePath: + wb_path_v0 = await subfolder_provider.validate_path('/') + + assert wb_path_v1 == wb_path_v0 + assert wb_path_v1.identifier == subfolder_provider_fixtures['root_id'] @pytest.mark.aiohttpretty @pytest.mark.asyncio - async def test_validate_v1_path_file(self, provider, file_root_parent_metadata): - file_id = '75BFE374EBEB1211!150' - file_id = '1234' + async def test_validate_v1_path_folder(self, subfolder_provider, subfolder_provider_fixtures): + folder_id = subfolder_provider_fixtures['folder_id'] + folder_metadata = subfolder_provider_fixtures['folder_metadata'] - good_url = provider.build_url(file_id) + item_url = subfolder_provider._build_item_url(folder_id) + print('item url: {}'.format(item_url)) + aiohttpretty.register_json_uri('GET', item_url, body=folder_metadata, status=200) - aiohttpretty.register_json_uri('GET', good_url, body=file_root_parent_metadata, status=200) + folder_path = '/{}/'.format(folder_id) + folder_name = '/{}/'.format(folder_metadata['name']) + try: + wb_path_v1 = await subfolder_provider.validate_v1_path(folder_path) + except Exception as exc: + pytest.fail(str(exc)) + + assert str(wb_path_v1) == folder_name + assert wb_path_v1.identifier == folder_id + + wb_path_v0 = await subfolder_provider.validate_path(folder_path) + assert str(wb_path_v0) == folder_name + + assert wb_path_v1 == wb_path_v0 + + with pytest.raises(exceptions.NotFoundError) as exc: + await subfolder_provider.validate_v1_path(folder_path.rstrip('/')) + + @pytest.mark.aiohttpretty + @pytest.mark.asyncio + async def test_validate_v1_path_file_is_child(self, subfolder_provider, + subfolder_provider_fixtures): + """file is immediate child of provider base folder""" + file_id = subfolder_provider_fixtures['file_id'] + file_metadata = subfolder_provider_fixtures['file_metadata'] - wb_path_v1 = await provider.validate_v1_path('/' + file_id) + item_url = subfolder_provider._build_item_url(file_id) + print('item url: {}'.format(item_url)) + aiohttpretty.register_json_uri('GET', item_url, body=file_metadata, status=200) - assert str(wb_path_v1) == '/{}'.format(file_root_parent_metadata['name']) + file_path = '/{}'.format(file_id) + file_name = '/{}'.format(file_metadata['name']) + try: + wb_path_v1 = await subfolder_provider.validate_v1_path(file_path) + except Exception as exc: + pytest.fail(str(exc)) - wb_path_v0 = await provider.validate_path('/' + file_id) + assert str(wb_path_v1) == file_name + assert wb_path_v1.identifier == file_id - assert str(wb_path_v0) == '/{}'.format(file_root_parent_metadata['name']) + wb_path_v0 = await subfolder_provider.validate_path(file_path) + assert str(wb_path_v0) == file_name + + assert wb_path_v1 == wb_path_v0 + + with pytest.raises(exceptions.NotFoundError) as exc: + await subfolder_provider.validate_v1_path(file_path + '/') + + @pytest.mark.aiohttpretty + @pytest.mark.asyncio + async def test_validate_v1_path_file_is_grandchild(self, subfolder_provider, + subfolder_provider_fixtures): + """file is *not* immediate child of provider base folder""" + subfile_id = subfolder_provider_fixtures['subfile_id'] + subfile_metadata = subfolder_provider_fixtures['subfile_metadata'] + + item_url = subfolder_provider._build_item_url(subfile_id) + print('item url: {}'.format(item_url)) + aiohttpretty.register_json_uri('GET', item_url, body=subfile_metadata, status=200) + + root_url = subfolder_provider._build_item_url(subfolder_provider_fixtures['root_id']) + print('root url: {}'.format(root_url)) + aiohttpretty.register_json_uri('GET', root_url, + body=subfolder_provider_fixtures['root_metadata'], + status=200) + + subfile_path = '/{}'.format(subfile_id) + subfile_name = '/{}/{}'.format(subfolder_provider_fixtures['folder_metadata']['name'], + subfile_metadata['name']) + try: + wb_path_v1 = await subfolder_provider.validate_v1_path(subfile_path) + except Exception as exc: + pytest.fail(str(exc)) + assert str(wb_path_v1) == subfile_name + + wb_path_v0 = await subfolder_provider.validate_path(subfile_path) + assert str(wb_path_v0) == subfile_name assert wb_path_v1 == wb_path_v0 @pytest.mark.aiohttpretty @pytest.mark.asyncio - async def test_revalidate_path_base_has_id(self, provider, file_root_parent_metadata): - file_id = '1234' - file_name = 'elect-a.jpg' - parent_id = '75BFE374EBEB1211!107' - expected_path = OneDrivePath('/' + file_name, [None, file_id]) - base_path = OneDrivePath('/', [file_id]) + async def test_validate_v1_path_file_is_outside_root(self, subfolder_provider, + subfolder_provider_fixtures): + """file is outside of the base storage root""" + file_id = subfolder_provider_fixtures['outside_file_id'] + file_metadata = subfolder_provider_fixtures['outside_file_metadata'] - good_url = "https://api.onedrive.com/v1.0/drive/root%3A/{}/{}".format(file_name, file_name) - aiohttpretty.register_json_uri('GET', good_url, body=file_root_parent_metadata, status=200) + item_url = subfolder_provider._build_item_url(file_id) + aiohttpretty.register_json_uri('GET', item_url, body=file_metadata, status=200) - good_url = "https://api.onedrive.com/v1.0/drive/items/{}".format(parent_id) - aiohttpretty.register_json_uri('GET', good_url, body=file_root_parent_metadata, status=200) + root_url = subfolder_provider._build_item_url(subfolder_provider_fixtures['root_id']) + aiohttpretty.register_json_uri('GET', root_url, + body=subfolder_provider_fixtures['root_metadata'], + status=200) - good_url = "https://api.onedrive.com/v1.0/drive/items/{}".format(file_id) - aiohttpretty.register_json_uri('GET', good_url, body=file_root_parent_metadata, status=200) + file_path = '/{}'.format(file_id) + with pytest.raises(exceptions.NotFoundError) as exc: + await subfolder_provider.validate_v1_path(file_path) - actual_path = await provider.revalidate_path(base_path, file_name, False) + with pytest.raises(exceptions.NotFoundError) as exc: + await subfolder_provider.validate_path(file_path) - assert actual_path == expected_path + +class TestRevalidatePath: @pytest.mark.aiohttpretty @pytest.mark.asyncio - async def test_revalidate_path_does_not_exist(self, provider, file_root_parent_metadata): - file_id = '1234' - file_name = 'elect-a.jpg' - parent_id = '75BFE374EBEB1211!107' - expected_path = OneDrivePath('/' + file_name, [None, file_id]) - base_path = OneDrivePath('/', [file_id]) + async def test_revalidate_path_file(self, root_provider, root_provider_fixtures): + file_name = 'toes.txt' + file_id = root_provider_fixtures['file_id'] + root_id = 'root' - good_url = "https://api.onedrive.com/v1.0/drive/root%3A/{}/{}".format(file_name, file_name) - aiohttpretty.register_json_uri('GET', good_url, body=file_root_parent_metadata, status=200) + parent_path = OneDrivePath('/', _ids=[root_id]) + expected_path = OneDrivePath('/{}'.format(file_name), _ids=[root_id, file_id]) - good_url = "https://api.onedrive.com/v1.0/drive/items/{}".format(parent_id) - aiohttpretty.register_json_uri('GET', good_url, body=file_root_parent_metadata, status=200) + parent_url = root_provider._build_drive_url(*parent_path.api_identifier, expand='children') + aiohttpretty.register_json_uri('GET', parent_url, + body=root_provider_fixtures['root_metadata'], status=200) - good_url = "https://api.onedrive.com/v1.0/drive/items/{}".format(file_id) - aiohttpretty.register_json_uri('GET', good_url, body=file_root_parent_metadata, status=200) + actual_path = await root_provider.revalidate_path(parent_path, file_name, False) + assert actual_path == expected_path + + with pytest.raises(exceptions.NotFoundError) as exc: + await root_provider.revalidate_path(parent_path, file_name, True) + + @pytest.mark.aiohttpretty + @pytest.mark.asyncio + async def test_revalidate_path_folder(self, root_provider, root_provider_fixtures): + folder_name = 'teeth' + folder_id = root_provider_fixtures['folder_id'] + root_id = 'root' + + parent_path = OneDrivePath('/', _ids=[root_id]) + expected_path = OneDrivePath('/{}/'.format(folder_name), _ids=[root_id, folder_id]) - actual_path = await provider.revalidate_path(base_path, file_name, False) + parent_url = root_provider._build_drive_url(*parent_path.api_identifier, expand='children') + aiohttpretty.register_json_uri('GET', parent_url, + body=root_provider_fixtures['root_metadata'], status=200) + actual_path = await root_provider.revalidate_path(parent_path, folder_name, True) assert actual_path == expected_path + with pytest.raises(exceptions.NotFoundError) as exc: + await root_provider.revalidate_path(parent_path, folder_name, False) + @pytest.mark.aiohttpretty @pytest.mark.asyncio - async def test_revalidate_path_no_child_folders_sub_folder(self, provider, file_root_parent_metadata): - file_id = '1234' - file_name = 'elect-a.jpg' - parent_id = '75BFE374EBEB1211!107' - expected_path = OneDrivePath('/' + file_name, [None, file_id]) - base_path = OneDrivePath('/', prepend=parent_id) + async def test_revalidate_path_subfile(self, root_provider, root_provider_fixtures): + root_id = 'root' + parent_id = root_provider_fixtures['folder_id'] + subfile_id = root_provider_fixtures['subfile_id'] - good_url = "https://api.onedrive.com/v1.0/drive/root%3A/{}/{}".format(file_name, file_name) - aiohttpretty.register_json_uri('GET', good_url, body=file_root_parent_metadata, status=200) + parent_name = 'teeth' + subfile_name = 'bicuspid.txt' - good_url = "https://api.onedrive.com/v1.0/drive/items/{}".format(parent_id) - aiohttpretty.register_json_uri('GET', good_url, body=file_root_parent_metadata, status=200) + parent_path = OneDrivePath('/{}/'.format(parent_name), _ids=[root_id, parent_id]) + expected_path = OneDrivePath('/{}/{}'.format(parent_name, subfile_name), + _ids=[root_id, parent_id, subfile_id]) - actual_path = await provider.revalidate_path(base_path, file_name, False) + parent_url = root_provider._build_drive_url(*parent_path.api_identifier, expand='children') + aiohttpretty.register_json_uri('GET', parent_url, + body=root_provider_fixtures['folder_metadata'], status=200) + actual_path = await root_provider.revalidate_path(parent_path, subfile_name, False) assert actual_path == expected_path + with pytest.raises(exceptions.NotFoundError) as exc: + await root_provider.revalidate_path(parent_path, subfile_name, True) + + +class TestMetadata: @pytest.mark.aiohttpretty @pytest.mark.asyncio - async def test_revalidate_path_has_child_folders(self, provider, folder_object_metadata): - file_id = '1234' - file_name = 'elect-a.jpg' - parent_id = '75BFE374EBEB1211!107' - base_path = OneDrivePath('/sub1-b', prepend=parent_id) - expected_path = OneDrivePath('/sub1-b/' + file_name, [None, None, file_id]) + async def test_metadata_root(self, subfolder_provider, subfolder_provider_fixtures): - good_url = provider._build_root_url('drive/root:', 'ryan-test1', 'sub1-b', file_name) - aiohttpretty.register_json_uri('GET', good_url, body=folder_object_metadata, status=200) + path = OneDrivePath('/', _ids=(subfolder_provider_fixtures['root_id'], )) - good_url = "https://api.onedrive.com/v1.0/drive/items/{}".format(parent_id) - aiohttpretty.register_json_uri('GET', good_url, body=folder_object_metadata, status=200) + list_url = subfolder_provider._build_drive_url(*path.api_identifier, expand='children') + aiohttpretty.register_json_uri('GET', list_url, body=subfolder_provider_fixtures['root_metadata']) - assert '/sub1-b/' + file_name == str(expected_path) + result = await subfolder_provider.metadata(path) + assert len(result) == 2 + folder_metadata = result[0] + assert folder_metadata.kind == 'folder' + assert folder_metadata.name == 'crushers' -class TestMetadata: + file_metadata = result[1] + assert file_metadata.kind == 'file' + assert file_metadata.name == 'bicuspid.txt' + + @pytest.mark.aiohttpretty + @pytest.mark.asyncio + async def test_metadata_folder(self, subfolder_provider, subfolder_provider_fixtures): + folder_id = subfolder_provider_fixtures['folder_id'] + folder_metadata = subfolder_provider_fixtures['folder_metadata'] + folder_name = folder_metadata['name'] + path = OneDrivePath('/{}/'.format(folder_name), + _ids=(subfolder_provider_fixtures['root_id'], folder_id, )) + + list_url = subfolder_provider._build_drive_url(*path.api_identifier, expand='children') + aiohttpretty.register_json_uri('GET', list_url, body=folder_metadata) + + result = await subfolder_provider.metadata(path) + assert len(result) == 1 + + file_metadata = result[0] + assert file_metadata.kind == 'file' + assert file_metadata.name == 'molars.txt' @pytest.mark.aiohttpretty @pytest.mark.asyncio - async def test_metadata_root(self, provider, folder_object_metadata, folder_list_metadata): - path = OneDrivePath('/0/', _ids=(0, )) - logger.info('test_metadata path:{} provider.folder:{} provider:'.format(repr(path), repr(provider.folder), repr(provider))) + async def test_metadata_file(self, subfolder_provider, subfolder_provider_fixtures): + file_id = subfolder_provider_fixtures['file_id'] + file_metadata = subfolder_provider_fixtures['file_metadata'] + file_name = file_metadata['name'] + path = OneDrivePath('/{}'.format(file_name), + _ids=(subfolder_provider_fixtures['root_id'], file_id, )) - list_url = provider.build_url('root', expand='children') + list_url = subfolder_provider._build_drive_url(*path.api_identifier, expand='children') + aiohttpretty.register_json_uri('GET', list_url, body=file_metadata) - aiohttpretty.register_json_uri('GET', list_url, body=folder_list_metadata) + result = await subfolder_provider.metadata(path) + assert result.kind == 'file' + assert result.name == 'bicuspid.txt' - result = await provider.metadata(path) - assert len(result) == 3 +class TestRevisions: + @pytest.mark.asyncio @pytest.mark.aiohttpretty - def test_metadata_file_root_parent_names(self, provider, folder_object_metadata, file_root_parent_metadata): - path = OneDrivePath('/elect-b.jpg') - result = path.file_path(file_root_parent_metadata) + async def test_get_revisions(self, provider, revision_fixtures): + file_id = revision_fixtures['file_id'] + path = OneDrivePath('/bicuspids.txt', _ids=[revision_fixtures['root_id'], file_id]) + + revision_response = revision_fixtures['file_revisions'] + revisions_url = provider._build_drive_url('items', file_id, 'view.delta', + top=provider.MAX_REVISIONS) + aiohttpretty.register_json_uri('GET', revisions_url, body=revision_response) + + result = await provider.revisions(path) - assert result == '/elect-a.jpg' + assert len(result) == 1 + +class TestDownload: + + @pytest.mark.asyncio @pytest.mark.aiohttpretty + async def test_download_standard_file(self, provider, download_fixtures): + file_id = download_fixtures['file_id'] + path = OneDrivePath('/toes.txt', _ids=[download_fixtures['root_id'], file_id]) + + metadata_response = download_fixtures['file_metadata'] + metadata_url = provider._build_drive_url('items', file_id) + aiohttpretty.register_json_uri('GET', metadata_url, body=metadata_response) + + aiohttpretty.register_uri('GET', download_fixtures['file_download_url'], + body=download_fixtures['file_content'], + headers={'Content-Length': '11'}) + + response = await provider.download(path) + content = await response.read() + assert content == b'ten of them' + @pytest.mark.asyncio - async def test_metadata_material_path_has_slash(self, provider, file_root_parent_metadata): - path = OneDrivePath("/elect-a.jpg") + @pytest.mark.aiohttpretty + async def test_download_by_revision(self, provider, download_fixtures): + file_id = download_fixtures['file_id'] + path = OneDrivePath('/toes.txt', _ids=[download_fixtures['root_id'], file_id]) - assert path.materialized_path.startswith('/') + revision_response = download_fixtures['file_revisions'] + revisions_url = provider._build_drive_url('items', file_id, 'view.delta', + top=provider.MAX_REVISIONS) + aiohttpretty.register_json_uri('GET', revisions_url, body=revision_response) + + aiohttpretty.register_uri('GET', download_fixtures['file_revision_download_url'], + body=download_fixtures['file_content'], + headers={'Content-Length': '11'}) + + response = await provider.download(path, revision=download_fixtures['file_revision']) + content = await response.read() + assert content == b'ten of them' + + @pytest.mark.asyncio + async def test_download_no_such_file(self, provider): + od_path = OneDrivePath('/does-not-exists', _ids=[None, None]) + with pytest.raises(exceptions.DownloadError) as exc: + await provider.download(od_path) + @pytest.mark.asyncio @pytest.mark.aiohttpretty - def test_metadata_ids_padding(self, provider, folder_object_metadata, file_sub_folder_metadata): - od_path = OneDrivePath("/test.foo") - result = od_path.ids(file_sub_folder_metadata) - assert result == [None, None, None, file_sub_folder_metadata['parentReference']['id'], file_sub_folder_metadata['id']] + async def test_download_by_bad_revision(self, provider, download_fixtures): + file_id = download_fixtures['file_id'] + path = OneDrivePath('/toes.txt', _ids=[download_fixtures['root_id'], file_id]) + + revision_response = download_fixtures['file_revisions'] + revisions_url = provider._build_drive_url('items', file_id, 'view.delta', + top=provider.MAX_REVISIONS) + aiohttpretty.register_json_uri('GET', revisions_url, body=revision_response) + with pytest.raises(exceptions.NotFoundError) as exc: + await provider.download(path, revision='thisisafakerevision') + + @pytest.mark.asyncio @pytest.mark.aiohttpretty - def test_metadata_ids_no_padding(self, provider, folder_object_metadata, file_root_folder_metadata): - od_path = OneDrivePath("/test.foo") - result = od_path.ids(file_root_folder_metadata) - assert result == [None, file_root_folder_metadata['parentReference']['id'], file_root_folder_metadata['id']] + async def test_download_unexportable_file(self, provider, download_fixtures): + onenote_id = download_fixtures['onenote_id'] + path = OneDrivePath('/onenote', _ids=[download_fixtures['root_id'], onenote_id]) + metadata_response = download_fixtures['onenote_metadata'] + metadata_url = provider._build_drive_url('items', onenote_id) + aiohttpretty.register_json_uri('GET', metadata_url, body=metadata_response) + + with pytest.raises(exceptions.UnexportableFileTypeError) as exc: + await provider.download(path) + + @pytest.mark.asyncio @pytest.mark.aiohttpretty - def test_metadata_folder_ids_padding(self, provider, folder_sub_folder_metadata): - od_path = OneDrivePath("/test.foo") - result = od_path.ids(folder_sub_folder_metadata) - assert result == [None, None, None, folder_sub_folder_metadata['parentReference']['id'], folder_sub_folder_metadata['id']] + async def test_download_unexportable_by_revision(self, provider, download_fixtures): + onenote_id = download_fixtures['onenote_id'] + path = OneDrivePath('/onenote', _ids=[download_fixtures['root_id'], onenote_id]) + + revision_response = download_fixtures['onenote_revisions'] + revisions_url = provider._build_drive_url('items', onenote_id, 'view.delta', + top=provider.MAX_REVISIONS) + aiohttpretty.register_json_uri('GET', revisions_url, body=revision_response) + + with pytest.raises(exceptions.UnexportableFileTypeError) as exc: + await provider.download(path, revision=download_fixtures['onenote_revision']) + + +class TestReadOnlyProvider: + + @pytest.mark.asyncio + async def test_upload(self, provider): + with pytest.raises(exceptions.ReadOnlyProviderError) as e: + await provider.upload('/foo-file.txt') + assert e.value.code == 501 + + @pytest.mark.asyncio + async def test_delete(self, provider): + with pytest.raises(exceptions.ReadOnlyProviderError) as e: + await provider.delete() + assert e.value.code == 501 + + @pytest.mark.asyncio + async def test_move(self, provider): + with pytest.raises(exceptions.ReadOnlyProviderError) as e: + await provider.move() + assert e.value.code == 501 + + @pytest.mark.asyncio + async def test_copy_to(self, provider): + with pytest.raises(exceptions.ReadOnlyProviderError) as e: + await provider.copy(provider) + assert e.value.code == 501 + + def test_can_intra_move(self, provider): + assert provider.can_intra_move(provider) == False + + def test_can_intra_copy(self, provider): + assert provider.can_intra_copy(provider) == False + + +# leftover bits +class TestMisc: + def test_can_duplicate_name(self, provider): + assert provider.can_duplicate_names() == False diff --git a/waterbutler/core/exceptions.py b/waterbutler/core/exceptions.py index dcde5862d..24c5f76c6 100644 --- a/waterbutler/core/exceptions.py +++ b/waterbutler/core/exceptions.py @@ -231,6 +231,13 @@ def __init__(self, repo_name, is_user_error=True, **kwargs): is_user_error=is_user_error) +class UnexportableFileTypeError(DownloadError): + def __init__(self, path, message=None, is_user_error=True): + if not message: + message = 'The file "{}" is not exportable'.format(path) + super().__init__(message, code=HTTPStatus.BAD_REQUEST, is_user_error=is_user_error) + + async def exception_from_response(resp, error=UnhandledProviderError, **kwargs): """Build and return, not raise, an exception from a response object diff --git a/waterbutler/providers/onedrive/metadata.py b/waterbutler/providers/onedrive/metadata.py index 3ece9997b..aafd003da 100644 --- a/waterbutler/providers/onedrive/metadata.py +++ b/waterbutler/providers/onedrive/metadata.py @@ -1,12 +1,14 @@ +from waterbutler.core import utils from waterbutler.core import metadata class BaseOneDriveMetadata(metadata.BaseMetadata): def __init__(self, raw, path_obj): - print('++++++++++++++++++++++++++++++ BaseOneDriveMetadata.__init__') - print('+++++ raw: {}'.format(raw)) - print('+++++ path_obj: {}'.format(path_obj)) + """Figuring out the materialized path for a OneDrive entity can be a bit tricky. If the + base folder is not the provider root, we need to make sure to scrub out everything up to + and including the base folder. All this has been done already in building the + OneDrivePath object, so we'll just pass that in and save ourselves some trouble.""" super().__init__(raw) self._path_obj = path_obj @@ -16,21 +18,23 @@ def provider(self): @property def materialized_path(self): - return '/{}/{}'.format( - self.raw['parentReference']['path'].replace('/drive/root:/', ''), - self.raw['name'] - ) + return str(self._path_obj) @property def extra(self): return { - 'id': self.raw['id'], - 'parentReference': self.raw['parentReference']['path'] + 'id': self.raw.get('id'), + 'etag': self.raw.get('eTag'), + 'webView': self.raw.get('webUrl'), } - @property - def created_utc(self): - return None + def _json_api_links(self, resource) -> dict: + """Update JSON-API links to remove mutation actions""" + links = super()._json_api_links(resource) + for action in ['delete', 'upload', 'new_folder']: + if action in links: + links[action] = None + return links class OneDriveFolderMetadata(BaseOneDriveMetadata, metadata.BaseFolderMetadata): @@ -47,6 +51,24 @@ def path(self): def etag(self): return self.raw.get('eTag') + @property + def extra(self): + """OneDrive provides modified and creation times for folders. Most providers do not + so we'll stuff this into the ``extra`` properties.""" + + modified = self.raw.get('lastModifiedDateTime', None) + if modified is not None: + modified = utils.normalize_datetime(modified) + + created = self.raw.get('createdDateTime', None) + if created is not None: + created = utils.normalize_datetime(created) + + return dict(super().extra, **{ + 'modified_utc': modified, + 'created_utc': created, + }) + class OneDriveFileMetadata(BaseOneDriveMetadata, metadata.BaseFileMetadata): @@ -60,7 +82,7 @@ def path(self): @property def size(self): - return self.raw.get('size') + return int(self.raw.get('size')) @property def modified(self): @@ -72,18 +94,17 @@ def content_type(self): return self.raw['file'].get('mimeType') return 'application/octet-stream' - @property - def extra(self): - return { - 'id': self.raw.get('id'), - 'etag': self.raw.get('eTag'), - 'webView': self.raw.get('webUrl'), - } - @property def etag(self): return self.raw['eTag'] + @property + def created_utc(self): + created = self.raw.get('createdDateTime', None) + if created is not None: + created = utils.normalize_datetime(created) + return created + class OneDriveRevisionMetadata(metadata.BaseFileRevisionMetadata): diff --git a/waterbutler/providers/onedrive/path.py b/waterbutler/providers/onedrive/path.py index 4568b9b51..5dce9c01a 100644 --- a/waterbutler/providers/onedrive/path.py +++ b/waterbutler/providers/onedrive/path.py @@ -1,5 +1,5 @@ from itertools import repeat -from urllib.parse import urlparse +from urllib import parse as urlparse from waterbutler.core.path import WaterButlerPath @@ -8,18 +8,72 @@ class OneDrivePath(WaterButlerPath): """OneDrive specific WaterButlerPath class to handle some of the idiosyncrasies of file paths in OneDrive.""" - def file_path(self, data): - parent_path = data['parentReference']['path'].replace('/drive/root:', '') + @classmethod + def new_from_response(cls, response, base_folder_id, base_folder_metadata=None): + """Build a new `OneDrivePath` object from a OneDrive API response representing a file or + folder entity. Requires the ID of the provider base folder. Requires base folder metadata + if base folder is neither the provider root nor the immediate parent of the entity being + built. + + :param dict response: metadata for the file or folder from OneDrive API + :param str base_folder_id: ID of the provider root + :param dict base_folder_metadata: metadata for the provider root from OneDrive API + :rtype OneDrivePath: + :return: a new OneDrivePath object representing the entity in `response` + """ + + if ( + base_folder_id not in ('root', response['parentReference']['id']) and + base_folder_metadata is None + ): + raise Exception('Need metadata for base folder to built correct OneDrivePath') + + parent_path = urlparse.unquote( + response['parentReference']['path'].replace('/drive/root:', '')) if (len(parent_path) == 0): - names = '/{}'.format(data['name']) + names = ['', response['name']] else: - names = '{}/{}'.format(parent_path, data['name']) - return names - - def ids(self, data): - ids = [data['parentReference']['id'], data['id']] - url_segment_count = len(urlparse(self.file_path(data)).path.split('/')) - if (len(ids) < url_segment_count): - for x in repeat(None, url_segment_count - len(ids)): + names = parent_path.split('/') + [response['name']] + + ids = [response['parentReference']['id'], response['id']] + if (len(ids) < len(names)): + for x in repeat(None, len(names) - len(ids)): ids.insert(0, x) - return ids + + is_folder = response.get('folder', None) is not None + + nbr_parts_to_keep = len(names) + if base_folder_metadata is not None: # need to sanitize base_folder and below + # calculate depth of base folder below drive root + # in drive root: 0 + # in subfolder of drive root: 1 + # IS drive root: shouldn't happen + # etc. + base_folder_depth = base_folder_metadata['parentReference']['path'].replace( + '/drive/root:', '' + ).count('/') + 1 + nbr_parts_to_keep = len(names) - base_folder_depth + elif base_folder_id != 'root': # immediate parent is base folder. sanitize + nbr_parts_to_keep = 2 + else: # base folder is root, no need to sanitize + pass + + if nbr_parts_to_keep < len(names): + keep_idx = nbr_parts_to_keep * -1 + names = names[keep_idx:] + ids = ids[keep_idx:] + + names[0] = '' + ids[0] = base_folder_id # redundant for middle case, but so what. + + return cls('/'.join(names), _ids=ids, folder=is_folder) + + @property + def api_identifier(self): + """Convenience method. OneDrive API endpoints are ``root`` when the path is the root and + ``items/$id`` when the path is a file or non-root folder.""" + if self.identifier is None: + return None + if self.identifier == 'root': + return ('root', ) + return ('items', self.identifier, ) diff --git a/waterbutler/providers/onedrive/provider.py b/waterbutler/providers/onedrive/provider.py index 12efb29a0..1e16fbfe7 100644 --- a/waterbutler/providers/onedrive/provider.py +++ b/waterbutler/providers/onedrive/provider.py @@ -1,5 +1,8 @@ -import http +import json +import typing import logging +from http import HTTPStatus +from urllib import parse as urlparse from waterbutler.core import streams from waterbutler.core import provider @@ -7,9 +10,9 @@ from waterbutler.providers.onedrive import settings from waterbutler.providers.onedrive.path import OneDrivePath -from waterbutler.providers.onedrive.metadata import OneDriveFileMetadata -from waterbutler.providers.onedrive.metadata import OneDriveFolderMetadata -from waterbutler.providers.onedrive.metadata import OneDriveRevisionMetadata +from waterbutler.providers.onedrive.metadata import (OneDriveFileMetadata, + OneDriveFolderMetadata, + OneDriveRevisionMetadata) logger = logging.getLogger(__name__) @@ -17,170 +20,270 @@ class OneDriveProvider(provider.BaseProvider): """Provider for the Microsoft OneDrive cloud storage service. - API docs: https://dev.onedrive.com/README.htm + This provider is currently **read-only** and does not contain write support. + This provider uses **ID-based paths**. Special drives: https://dev.onedrive.com/resources/drive.htm#tasks-on-drive-resources + **Auth:** - API:: + * auth: ``{"name": "username", "email": "username@example.com"}`` - Get folder contents: If folder is root, api path is ``/drive/root/children``. If folder - is not root, api path is ``/drive/items/$item-id/children`. + * credentials: ``{"token": "EWaa932BEN32042094DNFWJ40234=="}`` + + * settings: ``{"folder": "/foo/"}`` + + **API:** + + * Docs: https://dev.onedrive.com/README.htm + + * Get folder contents: If folder is root, api path is ``/drive/root/children``. If folder + is not root, api path is ``/drive/items/$item-id/children``. + + **Quirks:** + + * Special characters allowed in file and folder names:: + + `~!@#$%^&()-_=+[]{};', + + * Special characters *not* allowed in file and folder names:: + + "<>/?*:\| + + * File and folder names may not end with a period. """ NAME = 'onedrive' BASE_URL = settings.BASE_URL + MAX_REVISIONS = 250 + + dont_escape_these = ",;[]'$#@&!~()+-_=:/" + + # ========== __init__ ========== + def __init__(self, auth, credentials, settings): + logger.debug('__init__ auth::{} settings::{}'.format(auth, settings)) super().__init__(auth, credentials, settings) self.token = self.credentials['token'] self.folder = self.settings['folder'] - logger.info("__init__ credentials:{} settings:{}".format( - repr(credentials), repr(settings))) + + # ========== properties ========== @property - def default_headers(self): + def default_headers(self) -> dict: """Set Authorization header with access token from auth provider. API docs: https://dev.onedrive.com/auth/msa_oauth.htm """ return {'Authorization': 'bearer {}'.format(self.token)} - async def validate_v1_path(self, path, **kwargs): + # ========== methods ========== + + async def validate_v1_path(self, path: str, **kwargs) -> OneDrivePath: + """validate that ``path`` exists and matches the implicit semantics. + + See `provider.BaseProvider.validate_v1_path` for more. + + :param str path: A string representing the requested path. This will be everthing after + the provider name in the url. + :param dict \*\*kwargs: Query parameters and other parameters splatted into the call. + :raises: NotFoundError + :rtype: OneDrivePath + :return: a OneDrivePath object representing the new path. + """ + logger.debug('validate_v1_path self::{} path::{} kwargs::{}'.format(repr(self), + path, kwargs)) + if path == '/': return OneDrivePath(path, _ids=[self.folder]) - logger.info('validate_v1_path self::{} path::{} url:{}'.format( - repr(self), repr(path), self.build_url(path))) - resp = await self.make_request( - 'GET', self.build_url(path), + 'GET', self._build_item_url(path), expects=(200, ), throws=exceptions.MetadataError ) - + logger.debug('validate_v1_path resp::{}'.format(repr(resp))) data = await resp.json() - - od_path = OneDrivePath(path) - names = od_path.file_path(data) - ids = od_path.ids(data) - - wb_path = OneDrivePath(names, _ids=ids, folder=path.endswith('/')) - logger.info('wb_path::{} IDs:{}'.format(repr(wb_path._parts), repr(ids))) - return wb_path - - async def validate_path(self, path, **kwargs): - logger.info('validate_path self::{} path::{}'.format(repr(self), path)) - return await self.validate_v1_path(path, **kwargs) - - async def revalidate_path(self, base, path, folder=None): - logger.info('revalidate_path base::{} path::{} base.id::{}'.format( - base._prepend, path, base.identifier)) - logger.info('revalidate_path self::{} base::{} path::{}'.format( - str(self), repr(base), repr(path))) - logger.info('revalidate_path base::{} path::{}'.format( - repr(base.full_path), repr(path))) - - od_path = OneDrivePath('/{}'.format(path)) - if (base.identifier is not None): - url = self.build_url(base.identifier) - resp = await self.make_request( - 'GET', url, + logger.debug('validate_v1_path data::{}'.format(json.dumps(data))) + + implicit_folder = path.endswith('/') + explicit_folder = data.get('folder', None) is not None + if implicit_folder != explicit_folder: + raise exceptions.NotFoundError(path) + + # If base folder isn't root or the immediate parent of the requested path, then we need + # to verify that it actually is an ancestor of path. Otherwise, a malicious user could + # try to get access to a file outside of the configured root. + base_folder = None + if self.folder != 'root' and self.folder != data['parentReference']['id']: + base_folder_resp = await self.make_request( + 'GET', self._build_item_url(self.folder), expects=(200, ), throws=exceptions.MetadataError ) - data = await resp.json() - folder_path = od_path.file_path(data) - url = self._build_root_url("drive/root:", folder_path, str(path)) - elif (base._prepend is None): - # in a sub-folder, no need to get the root id - url = self._build_root_url('drive/root:', base.full_path, str(path)) - else: - # root: get folder name and build path from it - url = self.build_url(base._prepend) - resp = await self.make_request( - 'GET', url, + logger.debug('validate_v1_path base_folder_resp::{}'.format(repr(base_folder_resp))) + base_folder = await base_folder_resp.json() + logger.debug('validate_v1_path base_folder::{}'.format(json.dumps(base_folder))) + + base_full_path = urlparse.quote( + '{}/{}/'.format( + urlparse.unquote(base_folder['parentReference']['path']), + base_folder['name'] + ), + self.dont_escape_these + ) + + if not data['parentReference']['path'].startswith(base_full_path): + # the requested file is NOT a child of self.folder + raise exceptions.NotFoundError(path) + + od_path = OneDrivePath.new_from_response(data, self.folder, + base_folder_metadata=base_folder) + logger.debug('validate_v1_path od_path.parts::{}'.format(repr(od_path._parts))) + return od_path + + async def validate_path(self, path: str, **kwargs) -> OneDrivePath: + logger.debug('validate_path self::{} path::{} kwargs::{}'.format(repr(self), path, kwargs)) + + if path == '/': + return OneDrivePath(path, _ids=[self.folder]) + + resp = await self.make_request( + 'GET', self._build_item_url(path), + expects=(200, ), + throws=exceptions.MetadataError + ) + logger.debug('validate_path resp::{}'.format(repr(resp))) + data = await resp.json() + logger.debug('validate_path data::{}'.format(json.dumps(data))) + + # If base folder isn't root or the immediate parent of the requested path, then we need + # to verify that it actually is an ancestor of path. Otherwise, a malicious user could + # try to get access to a file outside of the configured root. + base_folder = None + if self.folder != 'root' and self.folder != data['parentReference']['id']: + base_folder_resp = await self.make_request( + 'GET', self._build_item_url(self.folder), expects=(200, ), throws=exceptions.MetadataError ) - data = await resp.json() - url = self._build_root_url("drive/root:", od_path.file_path(data), str(path)) + logger.debug('validate_path base_folder_resp::{}'.format(repr(base_folder_resp))) + base_folder = await base_folder_resp.json() + logger.debug('validate_path base_folder::{}'.format(json.dumps(base_folder))) + + base_full_path = urlparse.quote( + '{}/{}/'.format( + urlparse.unquote(base_folder['parentReference']['path']), + base_folder['name'] + ), + self.dont_escape_these + ) - resp = await self.make_request( + if not data['parentReference']['path'].startswith(base_full_path): + # the requested file is NOT a child of self.folder + raise exceptions.NotFoundError(path) # TESTME + + od_path = OneDrivePath.new_from_response(data, self.folder, + base_folder_metadata=base_folder) + logger.debug('validate_path od_path.parts::{}'.format(repr(od_path._parts))) + return od_path + + async def revalidate_path(self, # type: ignore + base: OneDrivePath, + path: str, + folder: bool=None) -> OneDrivePath: + """Take a string file/folder name ``path`` and return a OneDrivePath object + representing this file under ``base``. + + Since the OneDrive provider is currently readonly, the only place that calls this is + `core.provider._file_folder_op`. The base object passed there will always have an + identifier. Once write support is added to this provider, that will no longer be the + case. + + This probably isn't necessary for RO, and could probably be replaced by + `path_from_metadata`. + """ + logger.debug('revalidate_path base::{} path::{} base.id::{} folder::{}'.format( + base, path, base.identifier, folder)) + + base_url = self._build_drive_url(*base.api_identifier, expand='children') + base_resp = await self.make_request( 'GET', - url, - expects=(200, 404, ), - throws=exceptions.ProviderError + base_url, + expects=(200, ), + throws=exceptions.MetadataError ) + logger.debug('revalidate_path base_resp::{}'.format(repr(base_resp))) + base_data = await base_resp.json() + logger.debug('revalidate_path base_data::{}'.format(json.dumps(base_data))) - if (resp.status == 404): - ids = None - folder = False - await resp.release() - else: - data = await resp.json() - ids = od_path.ids(data)[-1] - folder = ('folder' in data.keys()) + child_id = None + for child in base_data['children']: + if child['name'] == path and (child.get('folder', None) is not None) == folder: + child_id = child['id'] + break - return base.child(path, _id=ids, folder=folder) + if child_id is None: + raise exceptions.NotFoundError(path) - async def metadata(self, path, revision=None, **kwargs): + return base.child(path, _id=child_id, folder=folder) + + async def metadata(self, path: OneDrivePath, **kwargs): # type: ignore """Fetch metadata for the file or folder identified by ``path``. API docs: https://dev.onedrive.com/items/get.htm - :param OneDrivePath path: - :param str revision: default ``None`` + :param OneDrivePath path: the file or folder to fetch metadata for :rtype: OneDriveMetadata :rtype: list(OneDriveFileMetadata|OneDriveFolderMetadata) :return: either a OneDriveFileMetada for a single file or an array of either - ``OneDriveFileMetadata` or `OneDriveFolderMetadata` objects + `OneDriveFileMetadata` or `OneDriveFolderMetadata` objects """ - logger.info('metadata identifier::{} path::{} revision::{}'.format( - repr(path.identifier), repr(path), repr(revision))) + logger.debug('metadata identifier::{} path::{}'.format(path.identifier, path)) - if path.api_identifier is None: + if path.api_identifier is None: # TESTME raise exceptions.NotFoundError(str(path)) - url = self.build_url(path.api_identifier, expand='children') - logger.info("metadata url::{}".format(repr(url))) + url = self._build_drive_url(*path.api_identifier, expand='children') + logger.debug("metadata url::{}".format(repr(url))) resp = await self.make_request( 'GET', url, expects=(200, ), throws=exceptions.MetadataError ) - logger.info("metadata resp::{}".format(repr(resp))) + logger.debug("metadata resp::{}".format(repr(resp))) data = await resp.json() - logger.info("metadata data::{}".format(repr(data))) + logger.debug("metadata data::{}".format(json.dumps(data))) if data.get('deleted'): - raise exceptions.MetadataError( + raise exceptions.MetadataError( # TESTME "Could not retrieve {kind} '{path}'".format( kind='folder' if data['folder'] else 'file', path=path, ), - code=http.client.NOT_FOUND, + code=HTTPStatus.NOT_FOUND, ) return self._construct_metadata(data) - async def revisions(self, path, **kwargs): + async def revisions(self, # type: ignore + path: OneDrivePath, + **kwargs) -> typing.List[OneDriveRevisionMetadata]: """Get a list of revisions for the file identified by ``path``. API docs: https://dev.onedrive.com/items/view_delta.htm - As of May 20, 2016: for files, the latest state is returned. There is not a list of changes - for the file. - - :param OneDrivePath path: a `OneDrivePath` object representing the file to get revisions for + :param OneDrivePath path: the file to get revisions for :rtype: list(OneDriveRevisionMetadata) :return: a list of `OneDriveRevisionMetadata` objects """ + logger.debug('revisions path::{} path.id::{} kwargs::{}'.format(path, path.identifier, + kwargs)) data = await self._revisions_json(path, **kwargs) - logger.info('revisions: data::{}'.format(data['value'])) return [ OneDriveRevisionMetadata(item) @@ -188,55 +291,84 @@ async def revisions(self, path, **kwargs): if not item.get('deleted') ] - async def download(self, path, revision=None, range=None, **kwargs): - """ OneDrive API Reference: https://dev.onedrive.com/items/download.htm """ - logger.info('folder:: {} revision::{} path.identifier:{} ' - 'path:{} path.parts:{}'.format( - self.folder, revision, path.identifier, repr(path), repr(path._parts))) + async def download(self, # type: ignore + path: OneDrivePath, + revision: str=None, + range: typing.Tuple[int, int]=None, + **kwargs) -> streams.ResponseStreamReader: + """Download the file identified by ``path``. If ``revision`` is not ``None``, get + the file at the version identified by ``revision``. + + API docs: https://dev.onedrive.com/items/download.htm + + :param str path: The path to the file on OneDrive + :param str revision: The revision of the file to download. If ``None``, download latest. + :param dict \*\*kwargs: Ignored + :raises: :class:`waterbutler.core.exceptions.DownloadError` + :rtype: waterbutler.core.streams.ResponseStreamReader + :return: a stream of the contents of the file + """ + logger.debug('download path::{} path.identifier::{} revision::{} range::{} ' + 'kwargs::{}'.format(path, path.identifier, revision, range, kwargs)) if path.identifier is None: raise exceptions.DownloadError('"{}" not found'.format(str(path)), code=404) - downloadUrl = None + download_url = None if revision: items = await self._revisions_json(path) for item in items['value']: if item['eTag'] == revision: - downloadUrl = item['@content.downloadUrl'] + try: + download_url = item['@content.downloadUrl'] + except KeyError: + raise exceptions.UnexportableFileTypeError(str(path)) break else: - url = self._build_content_url(path.identifier) - logger.info('url::{}'.format(url)) - metaData = await self.make_request( + # TODO: we should be able to get the download url from validate_v1_path + metadata_resp = await self.make_request( 'GET', - url, + self._build_drive_url(*path.api_identifier), expects=(200, ), throws=exceptions.MetadataError ) - data = await metaData.json() - logger.info('data::{} downloadUrl::{}'.format(data, downloadUrl)) - downloadUrl = data['@content.downloadUrl'] - if downloadUrl is None: + logger.debug('download metadata_resp::{}'.format(repr(metadata_resp))) + metadata = await metadata_resp.json() + logger.debug('download metadata::{}'.format(json.dumps(metadata))) + + try: + package_type = metadata['package']['type'] + except KeyError: + pass + else: + if package_type == 'oneNote': + raise exceptions.UnexportableFileTypeError(str(path)) + + download_url = metadata.get('@content.downloadUrl', None) + + if download_url is None: raise exceptions.NotFoundError(str(path)) - resp = await self.make_request( + logger.debug('download download_url::{}'.format(download_url)) + download_resp = await self.make_request( 'GET', - downloadUrl, + download_url, range=range, expects=(200, 206), headers={'accept-encoding': ''}, throws=exceptions.DownloadError, ) + logger.debug('download download_resp::{}'.format(repr(download_resp))) - return streams.ResponseStreamReader(resp) + return streams.ResponseStreamReader(download_resp) - def can_duplicate_names(self): + def can_duplicate_names(self) -> bool: return False - def can_intra_move(self, other, path=None): + def can_intra_move(self, other, path=None) -> bool: return False - def can_intra_copy(self, other, path=None): + def can_intra_copy(self, other, path=None) -> bool: return False async def upload(self, *args, **kwargs): @@ -252,49 +384,50 @@ async def move(self, *args, **kwargs): async def copy(self, dest_provider, *args, **kwargs): if dest_provider.NAME == self.NAME: raise exceptions.ReadOnlyProviderError(self.NAME) - return await super().copy(dest_provider, *args, **kwargs) + return await super().copy(dest_provider, *args, **kwargs) # TESTME + + # ========== utility methods ========== + def _build_drive_url(self, *segments, **query) -> str: + return provider.build_url(settings.BASE_DRIVE_URL, *segments, **query) - def _construct_metadata(self, data): + def _build_item_url(self, *segments, **query) -> str: + return provider.build_url(settings.BASE_DRIVE_URL, 'items', *segments, **query) + + def _construct_metadata(self, data: dict): + """Take a file/folder metadata response from OneDrive and return a `OneDriveFileMetadata` + object if the repsonse represents a file or a list of `OneDriveFileMetadata` and + `OneDriveFolderMetadata` objects if the response represents a folder. """ if 'folder' in data.keys(): ret = [] if 'children' in data.keys(): for item in data['children']: if 'folder' in item.keys(): - ret.append(OneDriveFolderMetadata(item, self.folder)) + ret.append(OneDriveFolderMetadata(item, self.folder)) # type: ignore else: - ret.append(OneDriveFileMetadata(item, self.folder)) + ret.append(OneDriveFileMetadata(item, self.folder)) # type: ignore return ret return OneDriveFileMetadata(data, self.folder) - def _build_root_url(self, *segments, **query): - return provider.build_url(settings.BASE_ROOT_URL, *segments, **query) - - def _build_content_url(self, *segments, **query): - return provider.build_url(settings.BASE_CONTENT_URL, *segments, **query) - - async def _revisions_json(self, path, **kwargs): - """ + async def _revisions_json(self, path: OneDrivePath, **kwargs) -> dict: + """Fetch a list of revisions for the file at ``path``. API docs: https://dev.onedrive.com/items/view_delta.htm - As of May 20, 2016: for files, the latest state is returned. - There is not a list of changes for the file. - - :param OneDrivePath path: The path to create a folder at - :rtype: `OneDriveFolderMetadata` - :return: a `OneDriveFolderMetadata` object representing the new folder + :param OneDrivePath path: the path of the file to get revisions for + :rtype: dict + :return: list of revision metadata under a ``value`` key """ - if path.identifier is None: - raise exceptions.NotFoundError(str(path)) - response = await self.make_request( + + resp = await self.make_request( 'GET', - self.build_url(path.identifier, 'view.delta', top=250), + self._build_drive_url(*path.api_identifier, 'view.delta', top=self.MAX_REVISIONS), expects=(200, ), throws=exceptions.RevisionsError ) - data = await response.json() - logger.info('revisions: data::{}'.format(data['value'])) + logger.debug('_revisions_json: resp::{}'.format(repr(resp))) + data = await resp.json() + logger.debug('_revisions_json: data::{}'.format(json.dumps(data))) return data diff --git a/waterbutler/providers/onedrive/settings.py b/waterbutler/providers/onedrive/settings.py index 4510487a3..048e637f3 100644 --- a/waterbutler/providers/onedrive/settings.py +++ b/waterbutler/providers/onedrive/settings.py @@ -3,8 +3,7 @@ config = settings.child('ONEDRIVE_PROVIDER_CONFIG') -BASE_URL = config.get('BASE_URL', 'https://api.onedrive.com/v1.0/drive/items/') -BASE_CONTENT_URL = config.get('BASE_CONTENT_URL', 'https://api.onedrive.com/v1.0/drive/items/') -BASE_ROOT_URL = config.get('BASE_ROOT_URL', 'https://api.onedrive.com/v1.0') +BASE_URL = config.get('BASE_URL', 'https://api.onedrive.com/v1.0') +BASE_DRIVE_URL = config.get('BASE_DRIVE_URL', 'https://api.onedrive.com/v1.0/drive') ONEDRIVE_COPY_ITERATION_COUNT = int(config.get('ONEDRIVE_COPY_ITERATION_COUNT', 30)) ONEDRIVE_COPY_SLEEP_INTERVAL = int(config.get('ONEDRIVE_COPY_SLEEP_INTERVAL', 3))