Skip to content

Commit 321e094

Browse files
authored
Merge pull request #286 from nextcloud/fix/accepted-output-keys
fix(tools): accept slide_deck_source in task outputs
2 parents 4f24b64 + 1c9eb03 commit 321e094

2 files changed

Lines changed: 83 additions & 1 deletion

File tree

‎ex_app/lib/all_tools/lib/task_processing.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class Response(BaseModel):
2323
task: Task
2424

2525

26-
ACCEPTED_OUTPUT_KEYS = ["file", "output", "images", "slide_deck", "sources"]
26+
ACCEPTED_OUTPUT_KEYS = ["file", "output", "images", "slide_deck", "slide_deck_source", "sources"]
2727

2828

2929
async def run_task(nc: AsyncNextcloudApp, type, task_input):

‎tests/test_task_processing_keys.py‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
2+
# SPDX-License-Identifier: AGPL-3.0-or-later
3+
"""Output-key whitelist of run_task: which task output keys are accepted.
4+
5+
Run standalone:
6+
7+
PYTHONPATH=. python tests/test_task_processing_keys.py
8+
9+
or via pytest when available. The network layer is stubbed: the fake app
10+
serves one already-successful task, so run_task finishes after a single
11+
scheduling call and reaches the output-key check under test.
12+
"""
13+
import asyncio
14+
import os
15+
import sys
16+
17+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18+
19+
from ex_app.lib.all_tools.lib.task_processing import run_task # noqa: E402
20+
21+
22+
class _FakeNextcloudApp:
23+
"""Serves one immediately-successful scheduled task with the given output dict."""
24+
25+
def __init__(self, output):
26+
self.output = output
27+
28+
async def log(self, level, content):
29+
pass
30+
31+
async def ocs(self, method, path, json=None):
32+
return {
33+
"task": {
34+
"id": 7,
35+
"status": "STATUS_SUCCESSFUL",
36+
"output": self.output,
37+
}
38+
}
39+
40+
41+
def _run_task_with_output(output):
42+
nc = _FakeNextcloudApp(output)
43+
return asyncio.run(run_task(nc, "richdocuments:slide_deck_generation", {"text": "a deck"}))
44+
45+
46+
def test_accepted_output_keys_pass():
47+
for key in ("file", "output", "images", "slide_deck", "sources"):
48+
task = _run_task_with_output({key: 42})
49+
assert task.output == {key: 42}, key
50+
51+
52+
def test_slide_deck_source_passes_alone():
53+
# before the whitelist learned this key, an output carrying only it was rejected
54+
task = _run_task_with_output({"slide_deck_source": 43})
55+
assert task.output == {"slide_deck_source": 43}
56+
57+
58+
def test_slide_deck_source_passes_alongside_slide_deck():
59+
task = _run_task_with_output({"slide_deck": 1, "slide_deck_source": 2})
60+
assert task.output == {"slide_deck": 1, "slide_deck_source": 2}
61+
62+
63+
def test_unknown_output_key_still_raises():
64+
try:
65+
_run_task_with_output({"definitely_not_an_output_key": 1})
66+
except Exception as exc:
67+
assert "Expected one of" in str(exc)
68+
return
69+
raise AssertionError("expected run_task to reject an output with no accepted key")
70+
71+
72+
if __name__ == "__main__":
73+
failed = 0
74+
for name, fn in sorted(globals().items()):
75+
if name.startswith("test_") and callable(fn):
76+
try:
77+
fn()
78+
print(f"PASS {name}")
79+
except Exception as exc:
80+
failed += 1
81+
print(f"FAIL {name}: {type(exc).__name__}: {exc}")
82+
sys.exit(1 if failed else 0)

0 commit comments

Comments
 (0)