test(12.1-01): add failing four-provider contract suite and fixtures

- Create test_cloud_provider_contract.py with parametrized suite for
  Nextcloud, WebDAV, Google Drive, OneDrive
- Assert canonical (connection_id, user_id, parent_ref=None, page_token=None)
  signature, CloudListing return type, trusted caller identity propagation,
  metadata normalization, pagination completeness, and forbidden-operation spies
- Add synthetic credential-free fixtures: nextcloud_root.xml, webdav_root.xml,
  google_drive_pages.json, onedrive_pages.json
- 9 tests fail for nextcloud (expected RED state — incompatible list_folder override)
- All webdav, google_drive, onedrive contract tests pass
This commit is contained in:
curo1305
2026-06-22 08:09:00 +02:00
parent 692600c755
commit eb68facd6c
5 changed files with 1277 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
{
"_comment": "Synthetic Google Drive fixture — no real credentials, tokens, or user data.",
"root_page1": {
"nextPageToken": "page2token_synthetic",
"files": [
{
"id": "synth_folder_a1b2c3",
"name": "Projects",
"mimeType": "application/vnd.google-apps.folder",
"modifiedTime": "2024-06-10T12:00:00Z"
},
{
"id": "synth_folder_d4e5f6",
"name": "Archive",
"mimeType": "application/vnd.google-apps.folder",
"modifiedTime": "2024-06-11T08:00:00Z"
},
{
"id": "synth_file_g7h8i9",
"name": "report.pdf",
"mimeType": "application/pdf",
"size": "204800",
"modifiedTime": "2024-06-12T14:00:00Z",
"md5Checksum": "abc123synth"
}
]
},
"root_page2": {
"files": [
{
"id": "synth_file_j0k1l2",
"name": "notes.txt",
"mimeType": "text/plain",
"size": "1024",
"modifiedTime": "2024-06-13T10:00:00Z",
"md5Checksum": "def456synth"
},
{
"id": "synth_native_m3n4o5",
"name": "Meeting Notes",
"mimeType": "application/vnd.google-apps.document",
"modifiedTime": "2024-06-14T09:30:00Z"
}
]
},
"nested_folder": {
"files": [
{
"id": "synth_nested_p6q7r8",
"name": "sub-document.docx",
"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"size": "51200",
"modifiedTime": "2024-06-15T11:00:00Z"
}
]
},
"trashed_excluded": {
"files": [
{
"id": "synth_file_s9t0u1",
"name": "visible.pdf",
"mimeType": "application/pdf",
"size": "10240",
"modifiedTime": "2024-06-16T10:00:00Z"
}
],
"_note": "trashed=false query param ensures trashed items are not returned"
},
"native_docs_nullable_size": {
"files": [
{
"id": "synth_doc_v2w3x4",
"name": "Spreadsheet",
"mimeType": "application/vnd.google-apps.spreadsheet",
"modifiedTime": "2024-06-17T13:00:00Z"
},
{
"id": "synth_slides_y5z6a7",
"name": "Presentation",
"mimeType": "application/vnd.google-apps.presentation",
"modifiedTime": "2024-06-18T14:00:00Z"
}
]
}
}
+164
View File
@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Synthetic Nextcloud Depth-1 PROPFIND fixture — no real credentials, hostnames, tokens, or user data.
Root path: /remote.php/dav/files/testuser/
Represents: 4 folders + 6 files directly in root.
Covers: root self-entry filtering, absolute hrefs, encoded spaces/unicode, missing optional props.
-->
<d:multistatus xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns" xmlns:oc="http://owncloud.org/ns">
<!-- Root self-entry — MUST be filtered out by the parser -->
<d:response>
<d:href>/remote.php/dav/files/testuser/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:displayname>testuser</d:displayname>
<d:getlastmodified>Mon, 01 Jan 2024 00:00:00 GMT</d:getlastmodified>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- Folder: Documents -->
<d:response>
<d:href>/remote.php/dav/files/testuser/Documents/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:displayname>Documents</d:displayname>
<d:getlastmodified>Mon, 10 Jun 2024 12:00:00 GMT</d:getlastmodified>
<oc:size>0</oc:size>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- Folder: Photos -->
<d:response>
<d:href>/remote.php/dav/files/testuser/Photos/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:displayname>Photos</d:displayname>
<d:getlastmodified>Tue, 11 Jun 2024 08:30:00 GMT</d:getlastmodified>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- Folder: My Work Files (contains spaces — encoded in href) -->
<d:response>
<d:href>/remote.php/dav/files/testuser/My%20Work%20Files/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:displayname>My Work Files</d:displayname>
<d:getlastmodified>Wed, 12 Jun 2024 09:00:00 GMT</d:getlastmodified>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- Folder: Unicode name: Café Designs -->
<d:response>
<d:href>/remote.php/dav/files/testuser/Caf%C3%A9%20Designs/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:displayname>Café Designs</d:displayname>
<d:getlastmodified>Thu, 13 Jun 2024 10:15:00 GMT</d:getlastmodified>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- File: report.pdf — all optional props present -->
<d:response>
<d:href>/remote.php/dav/files/testuser/report.pdf</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>report.pdf</d:displayname>
<d:getcontentlength>204800</d:getcontentlength>
<d:getcontenttype>application/pdf</d:getcontenttype>
<d:getlastmodified>Fri, 14 Jun 2024 14:00:00 GMT</d:getlastmodified>
<d:getetag>"abc123etag"</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- File: notes.txt — missing content-type and etag -->
<d:response>
<d:href>/remote.php/dav/files/testuser/notes.txt</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>notes.txt</d:displayname>
<d:getcontentlength>1024</d:getcontentlength>
<d:getlastmodified>Sat, 15 Jun 2024 10:00:00 GMT</d:getlastmodified>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- File: My Report Draft.docx — spaces in name -->
<d:response>
<d:href>/remote.php/dav/files/testuser/My%20Report%20Draft.docx</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>My Report Draft.docx</d:displayname>
<d:getcontentlength>51200</d:getcontentlength>
<d:getcontenttype>application/vnd.openxmlformats-officedocument.wordprocessingml.document</d:getcontenttype>
<d:getlastmodified>Sun, 16 Jun 2024 11:00:00 GMT</d:getlastmodified>
<d:getetag>"docx456etag"</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- File: image.png — size absent (should be None) -->
<d:response>
<d:href>/remote.php/dav/files/testuser/image.png</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>image.png</d:displayname>
<d:getcontenttype>image/png</d:getcontenttype>
<d:getlastmodified>Mon, 17 Jun 2024 12:30:00 GMT</d:getlastmodified>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- File: data.csv — all optional props absent (missing getcontenttype, getetag, size) -->
<d:response>
<d:href>/remote.php/dav/files/testuser/data.csv</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>data.csv</d:displayname>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- File: archive.zip — relative href (no leading slash) -->
<d:response>
<d:href>remote.php/dav/files/testuser/archive.zip</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>archive.zip</d:displayname>
<d:getcontentlength>102400</d:getcontentlength>
<d:getcontenttype>application/zip</d:getcontenttype>
<d:getlastmodified>Tue, 18 Jun 2024 09:00:00 GMT</d:getlastmodified>
<d:getetag>"zip789etag"</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
+93
View File
@@ -0,0 +1,93 @@
{
"_comment": "Synthetic OneDrive fixture — no real credentials, tokens, drive IDs, or user data.",
"root_page1": {
"value": [
{
"id": "synth_od_folder_a1b2",
"name": "Projects",
"folder": {"childCount": 3},
"parentReference": {"driveId": "synth-drive-id", "id": "root"},
"lastModifiedDateTime": "2024-06-10T12:00:00Z",
"eTag": "etag_synth_folder_a1b2"
},
{
"id": "synth_od_folder_c3d4",
"name": "My Archive",
"folder": {"childCount": 5},
"parentReference": {"driveId": "synth-drive-id", "id": "root"},
"lastModifiedDateTime": "2024-06-11T08:00:00Z"
},
{
"id": "synth_od_file_e5f6",
"name": "report.pdf",
"file": {"mimeType": "application/pdf"},
"size": 204800,
"parentReference": {"driveId": "synth-drive-id", "id": "root"},
"lastModifiedDateTime": "2024-06-12T14:00:00Z",
"eTag": "etag_synth_file_e5f6"
}
],
"@odata.nextLink": "https://graph.microsoft.com/v1.0/me/drive/root/children?$skip=3&$top=3"
},
"root_page2": {
"value": [
{
"id": "synth_od_file_g7h8",
"name": "My Report Draft.docx",
"file": {"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
"size": 51200,
"parentReference": {"driveId": "synth-drive-id", "id": "root"},
"lastModifiedDateTime": "2024-06-13T10:00:00Z"
},
{
"id": "synth_od_file_i9j0",
"name": "notes.txt",
"file": {"mimeType": "text/plain"},
"size": 1024,
"parentReference": {"driveId": "synth-drive-id", "id": "root"},
"lastModifiedDateTime": "2024-06-14T09:00:00Z",
"eTag": "etag_synth_file_i9j0"
}
]
},
"nested_children": {
"value": [
{
"id": "synth_od_file_k1l2",
"name": "sub-document.docx",
"file": {"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
"size": 25600,
"parentReference": {"driveId": "synth-drive-id", "id": "synth_od_folder_a1b2"},
"lastModifiedDateTime": "2024-06-15T11:00:00Z"
}
]
},
"nullable_metadata": {
"value": [
{
"id": "synth_od_file_m3n4",
"name": "unknown-file.bin",
"file": {},
"parentReference": {"driveId": "synth-drive-id", "id": "root"},
"lastModifiedDateTime": "2024-06-16T10:00:00Z"
}
]
},
"encoded_names": {
"value": [
{
"id": "synth_od_file_o5p6",
"name": "Annual Report & Summary.pdf",
"file": {"mimeType": "application/pdf"},
"size": 102400,
"parentReference": {"driveId": "synth-drive-id", "id": "root"},
"lastModifiedDateTime": "2024-06-17T13:00:00Z"
}
]
}
}
+105
View File
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Synthetic generic WebDAV Depth-1 PROPFIND fixture — no real credentials, hostnames, tokens.
Root path: /dav/
Represents: 2 folders + 3 files directly in root.
Covers: absolute/relative hrefs, missing optional props, foreign href rejection.
-->
<d:multistatus xmlns:d="DAV:">
<!-- Root self-entry — MUST be filtered out -->
<d:response>
<d:href>/dav/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:displayname>root</d:displayname>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- Folder: Archive -->
<d:response>
<d:href>/dav/Archive/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:displayname>Archive</d:displayname>
<d:getlastmodified>Mon, 10 Jun 2024 08:00:00 GMT</d:getlastmodified>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- Folder: Shared (relative href) -->
<d:response>
<d:href>dav/Shared/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:displayname>Shared</d:displayname>
<d:getlastmodified>Tue, 11 Jun 2024 09:00:00 GMT</d:getlastmodified>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- File: readme.txt — all optional props -->
<d:response>
<d:href>/dav/readme.txt</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>readme.txt</d:displayname>
<d:getcontentlength>512</d:getcontentlength>
<d:getcontenttype>text/plain</d:getcontenttype>
<d:getlastmodified>Wed, 12 Jun 2024 10:00:00 GMT</d:getlastmodified>
<d:getetag>"readmeetag"</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- File: backup.tar.gz — missing optional props -->
<d:response>
<d:href>/dav/backup.tar.gz</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>backup.tar.gz</d:displayname>
<d:getcontentlength>10240</d:getcontentlength>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- File: project.pdf — encoded spaces in href -->
<d:response>
<d:href>/dav/project%20notes.pdf</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>project notes.pdf</d:displayname>
<d:getcontentlength>204800</d:getcontentlength>
<d:getcontenttype>application/pdf</d:getcontenttype>
<d:getlastmodified>Thu, 13 Jun 2024 11:00:00 GMT</d:getlastmodified>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<!-- Foreign href — escapes configured root; MUST cause listing to be incomplete -->
<d:response>
<d:href>/other-root/escape.txt</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>escape.txt</d:displayname>
<d:getcontentlength>100</d:getcontentlength>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>