Skip to content

Commit 7aec9d3

Browse files
committed
test(files): cover every method that takes extra_properties
The integration tests only exercised listdir, by_path and find, so dropping the argument in by_id, list_by_criteria, trashbin_list or get_versions kept the suite green. Mutating each of the seven call sites now fails a test, in both the sync and the async API. While covering trashbin_list: it deduplicated the requested properties against PROPFIND_PROPERTIES only, so asking for one of the three TrashBin properties sent that element twice. Deduplicate against the full list instead and assert the request carries no duplicates. Also document the argument on by_path, which the other methods already had. Signed-off-by: Oleksandr Piskun <oleksandr2088@icloud.com>
1 parent 2821bcd commit 7aec9d3

4 files changed

Lines changed: 82 additions & 10 deletions

File tree

nc_py_api/files/files.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ def by_id(self, file_id: int | str | FsNode, extra_properties: Sequence[str] | N
8080
return result[0] if result else None
8181

8282
def by_path(self, path: str | FsNode, extra_properties: Sequence[str] | None = None) -> FsNode | None:
83-
"""Returns :py:class:`~nc_py_api.files.FsNode` by exact path if any."""
83+
"""Returns :py:class:`~nc_py_api.files.FsNode` by exact path if any.
84+
85+
:param extra_properties: additional WebDAV properties to request, e.g. ``["nc:has-preview"]``. They are
86+
returned in :py:attr:`~nc_py_api.files.FsNode.extra_properties`, and must use the ``d``, ``oc``
87+
or ``nc`` namespace.
88+
"""
8489
path = path.user_path if isinstance(path, FsNode) else path
8590
result = self.listdir(path, depth=0, exclude_self=False, extra_properties=extra_properties)
8691
return result[0] if result else None
@@ -311,8 +316,8 @@ def trashbin_list(self, extra_properties: Sequence[str] | None = None) -> list[F
311316
"nc:trashbin-filename",
312317
"nc:trashbin-original-location",
313318
"nc:trashbin-deletion-time",
314-
*_validate_extra_properties(extra_properties, PROPFIND_PROPERTIES),
315319
]
320+
properties += _validate_extra_properties(extra_properties, properties)
316321
return self._listdir(
317322
self._session.user, "", properties=properties, depth=1, exclude_self=False, prop_type=PropFindType.TRASHBIN
318323
)

nc_py_api/files/files_async.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,12 @@ async def by_id(self, file_id: int | str | FsNode, extra_properties: Sequence[st
8282
return result[0] if result else None
8383

8484
async def by_path(self, path: str | FsNode, extra_properties: Sequence[str] | None = None) -> FsNode | None:
85-
"""Returns :py:class:`~nc_py_api.files.FsNode` by exact path if any."""
85+
"""Returns :py:class:`~nc_py_api.files.FsNode` by exact path if any.
86+
87+
:param extra_properties: additional WebDAV properties to request, e.g. ``["nc:has-preview"]``. They are
88+
returned in :py:attr:`~nc_py_api.files.FsNode.extra_properties`, and must use the ``d``, ``oc``
89+
or ``nc`` namespace.
90+
"""
8691
path = path.user_path if isinstance(path, FsNode) else path
8792
result = await self.listdir(path, depth=0, exclude_self=False, extra_properties=extra_properties)
8893
return result[0] if result else None
@@ -319,8 +324,8 @@ async def trashbin_list(self, extra_properties: Sequence[str] | None = None) ->
319324
"nc:trashbin-filename",
320325
"nc:trashbin-original-location",
321326
"nc:trashbin-deletion-time",
322-
*_validate_extra_properties(extra_properties, PROPFIND_PROPERTIES),
323327
]
328+
properties += _validate_extra_properties(extra_properties, properties)
324329
return await self._listdir(
325330
await self._session.user,
326331
"",

tests/actual_tests/files_test.py

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,22 +1319,53 @@ def test_etag_is_accepted_by_server_as_is(nc_any):
13191319
nc_any.files.delete("test_etag_as_is.txt", not_fail=True)
13201320

13211321

1322-
def _test_extra_properties(node: FsNode):
1322+
# the trashbin and versions endpoints answer 404 for some properties, so less is expected from them
1323+
FULL_EXTRA_PROPERTIES = ("nc:has-preview", "oc:owner-id", "oc:share-types")
1324+
1325+
1326+
def _test_extra_properties(node: FsNode, expected: tuple[str, ...] = FULL_EXTRA_PROPERTIES):
1327+
for key in expected:
1328+
assert key in node.extra_properties, f"`{key}` missing from {sorted(node.extra_properties)}"
13231329
assert node.extra_properties["nc:has-preview"] in ("true", "false")
1324-
assert node.extra_properties["oc:owner-id"]
1325-
# `oc:share-types` is requested by default, so it shows up without being asked for
1326-
assert "oc:share-types" in node.extra_properties
13271330

13281331

13291332
def test_extra_properties(nc_any):
13301333
nc_any.files.delete("test_extra_props", not_fail=True)
13311334
nc_any.files.mkdir("test_extra_props")
13321335
try:
13331336
nc_any.files.upload("test_extra_props/a.txt", b"content")
1337+
nc_any.files.upload("test_extra_props/a.txt", b"content2") # a second version
1338+
nc_any.files.setfav("test_extra_props/a.txt", True)
13341339
extra = ["nc:has-preview", "oc:owner-id"]
1335-
_test_extra_properties(nc_any.files.by_path("test_extra_props/a.txt", extra_properties=extra))
1340+
node = nc_any.files.by_path("test_extra_props/a.txt", extra_properties=extra)
1341+
_test_extra_properties(node)
13361342
_test_extra_properties(nc_any.files.listdir("test_extra_props", extra_properties=extra)[0])
13371343
_test_extra_properties(nc_any.files.find(["like", "name", "a%"], "test_extra_props", extra_properties=extra)[0])
1344+
_test_extra_properties(nc_any.files.by_id(node, extra_properties=extra))
1345+
favorites = nc_any.files.list_by_criteria(properties=["favorite"], extra_properties=extra)
1346+
_test_extra_properties(next(i for i in favorites if i.name == "a.txt"))
1347+
versions = nc_any.files.get_versions(node, extra_properties=extra)
1348+
if versions: # versioning may be disabled on the server
1349+
_test_extra_properties(versions[0], ("nc:has-preview",))
1350+
nc_any.files.upload("test_extra_props/gone.txt", b"x")
1351+
nc_any.files.delete("test_extra_props/gone.txt")
1352+
trashed = [i for i in nc_any.files.trashbin_list(extra_properties=extra) if "gone.txt" in i.name]
1353+
_test_extra_properties(trashed[0], ("nc:has-preview",))
1354+
# a property the TrashBin listing requests anyway must not be sent twice
1355+
requested: list[str] = []
1356+
original_listdir = nc_any.files._listdir
1357+
nc_any.files._listdir = lambda user, path, **kw: (
1358+
requested.extend(kw["properties"]),
1359+
original_listdir(user, path, **kw),
1360+
)[1]
1361+
try:
1362+
nc_any.files.trashbin_list(extra_properties=["nc:trashbin-filename", "nc:has-preview"])
1363+
finally:
1364+
nc_any.files._listdir = original_listdir
1365+
assert len(requested) == len(
1366+
set(requested)
1367+
), f"duplicates: {sorted({i for i in requested if requested.count(i) > 1})}"
1368+
assert "nc:has-preview" in requested
13381369
# without asking for them, only the properties that are requested anyway are reported
13391370
plain = nc_any.files.by_path("test_extra_props/a.txt")
13401371
assert "nc:has-preview" not in plain.extra_properties
@@ -1343,6 +1374,7 @@ def test_extra_properties(nc_any):
13431374
nc_any.files.listdir("test_extra_props", extra_properties=["invalid:property"])
13441375
finally:
13451376
nc_any.files.delete("test_extra_props", not_fail=True)
1377+
nc_any.files.trashbin_cleanup()
13461378

13471379

13481380
@pytest.mark.asyncio(scope="session")
@@ -1351,14 +1383,28 @@ async def test_extra_properties_async(anc_any):
13511383
await anc_any.files.mkdir("test_extra_props")
13521384
try:
13531385
await anc_any.files.upload("test_extra_props/a.txt", b"content")
1386+
await anc_any.files.upload("test_extra_props/a.txt", b"content2")
1387+
await anc_any.files.setfav("test_extra_props/a.txt", True)
13541388
extra = ["nc:has-preview", "oc:owner-id"]
1355-
_test_extra_properties(await anc_any.files.by_path("test_extra_props/a.txt", extra_properties=extra))
1389+
node = await anc_any.files.by_path("test_extra_props/a.txt", extra_properties=extra)
1390+
_test_extra_properties(node)
13561391
_test_extra_properties((await anc_any.files.listdir("test_extra_props", extra_properties=extra))[0])
13571392
found = await anc_any.files.find(["like", "name", "a%"], "test_extra_props", extra_properties=extra)
13581393
_test_extra_properties(found[0])
1394+
_test_extra_properties(await anc_any.files.by_id(node, extra_properties=extra))
1395+
favorites = await anc_any.files.list_by_criteria(properties=["favorite"], extra_properties=extra)
1396+
_test_extra_properties(next(i for i in favorites if i.name == "a.txt"))
1397+
versions = await anc_any.files.get_versions(node, extra_properties=extra)
1398+
if versions:
1399+
_test_extra_properties(versions[0], ("nc:has-preview",))
1400+
await anc_any.files.upload("test_extra_props/gone.txt", b"x")
1401+
await anc_any.files.delete("test_extra_props/gone.txt")
1402+
trashed = [i for i in await anc_any.files.trashbin_list(extra_properties=extra) if "gone.txt" in i.name]
1403+
_test_extra_properties(trashed[0], ("nc:has-preview",))
13591404
plain = await anc_any.files.by_path("test_extra_props/a.txt")
13601405
assert "nc:has-preview" not in plain.extra_properties
13611406
with pytest.raises(ValueError):
13621407
await anc_any.files.listdir("test_extra_props", extra_properties=["invalid:property"])
13631408
finally:
13641409
await anc_any.files.delete("test_extra_props", not_fail=True)
1410+
await anc_any.files.trashbin_cleanup()

tests_unit/test_extra_properties.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,19 @@ def test_fs_node_without_extra_properties():
9090
def test_xml_attributes_are_not_reported_as_properties():
9191
node = _parse_record("files/admin/a.txt", [_prop_stat({"@xmlns:d": "DAV:", "nc:has-preview": "true"})])
9292
assert node.extra_properties == {"nc:has-preview": "true"}
93+
94+
95+
WITH_LOCKING = {"files": {"locking": "1.0"}} # `files.locking` advertised -> the lock properties are requested too
96+
97+
98+
def test_extra_properties_are_deduplicated_against_the_locking_ones():
99+
from nc_py_api.files._files import PROPFIND_LOCKING_PROPERTIES
100+
101+
requested = get_propfind_properties(WITH_LOCKING, ["nc:lock-owner", "nc:has-preview"])
102+
assert requested == [*PROPFIND_PROPERTIES, *PROPFIND_LOCKING_PROPERTIES, "nc:has-preview"]
103+
104+
105+
def test_extra_properties_come_after_the_default_ones():
106+
requested = get_propfind_properties(WITH_LOCKING, ["nc:has-preview"])
107+
assert requested[-1] == "nc:has-preview"
108+
assert len(requested) == len(set(requested))

0 commit comments

Comments
 (0)