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))