242 lines
8.7 KiB
Python
242 lines
8.7 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from govoplan_core.core.modules import DocumentationContext
|
|
from govoplan_files.backend.documentation import documentation_topics
|
|
from govoplan_files.backend.storage.connector_profiles import ConnectorProfile
|
|
|
|
|
|
class _Principal:
|
|
tenant_id = "tenant-1"
|
|
user = SimpleNamespace(id="user-1")
|
|
group_ids = frozenset({"group-1"})
|
|
|
|
def __init__(self, scopes: set[str]) -> None:
|
|
self.scopes = frozenset(scopes)
|
|
|
|
def has(self, scope: str) -> bool:
|
|
return scope in self.scopes
|
|
|
|
|
|
class FilesRuntimeDocumentationTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.session = Session()
|
|
|
|
def tearDown(self) -> None:
|
|
self.session.close()
|
|
|
|
def context(
|
|
self,
|
|
scopes: set[str],
|
|
*,
|
|
settings: object | None = None,
|
|
documentation_type: str = "user",
|
|
) -> DocumentationContext:
|
|
return DocumentationContext(
|
|
registry=object(),
|
|
principal=_Principal(scopes),
|
|
settings=settings,
|
|
session=self.session,
|
|
documentation_type=documentation_type, # type: ignore[arg-type]
|
|
)
|
|
|
|
def test_upload_tasks_state_exact_current_safe_limits(self) -> None:
|
|
settings = SimpleNamespace(
|
|
file_upload_max_bytes=7 * 1024 * 1024,
|
|
file_upload_zip_max_bytes=19 * 1024 * 1024,
|
|
)
|
|
with patch(
|
|
"govoplan_files.backend.documentation.visible_connector_profiles_for_actor",
|
|
return_value=[],
|
|
):
|
|
topics = {
|
|
topic.id: topic
|
|
for topic in documentation_topics(
|
|
self.context(
|
|
{"files:file:read", "files:file:upload"}, settings=settings
|
|
)
|
|
)
|
|
}
|
|
|
|
upload = topics["files.workflow.upload-managed-files"]
|
|
self.assertIn("7 MiB (7,340,032 bytes)", upload.body)
|
|
self.assertEqual(["files.list"], upload.metadata["help_contexts"])
|
|
self.assertEqual(
|
|
{"files:file:read", "files:file:upload"},
|
|
set(upload.conditions[0].required_scopes),
|
|
)
|
|
|
|
archive = topics["files.workflow.upload-and-unpack-zip"]
|
|
self.assertIn("19 MiB (19,922,944 bytes)", archive.body)
|
|
self.assertIn("7 MiB (7,340,032 bytes)", archive.body)
|
|
self.assertIn("1,000", archive.body)
|
|
self.assertIn("Actual extracted bytes are counted", archive.body)
|
|
|
|
def test_connector_task_requires_authority_and_a_visible_usable_profile(
|
|
self,
|
|
) -> None:
|
|
usable = ConnectorProfile(
|
|
id="private-profile-id",
|
|
label="Private profile label",
|
|
provider="webdav",
|
|
scope_type="tenant",
|
|
scope_id="tenant-1",
|
|
endpoint_url="https://private.example.invalid/secret-root",
|
|
base_path="/secret-root",
|
|
credential_mode="anonymous",
|
|
)
|
|
context = self.context({"files:file:read", "files:file:upload"})
|
|
with patch(
|
|
"govoplan_files.backend.documentation.visible_connector_profiles_for_actor",
|
|
return_value=[usable],
|
|
) as visible:
|
|
topics = {topic.id: topic for topic in documentation_topics(context)}
|
|
|
|
self.assertIn("files.workflow.import-managed-snapshot", topics)
|
|
self.assertNotIn("files.connector-import-unavailable", topics)
|
|
task = topics["files.workflow.import-managed-snapshot"]
|
|
self.assertEqual("configured", task.layer)
|
|
self.assertEqual(
|
|
["files.list", "files.connector-import"], task.metadata["help_contexts"]
|
|
)
|
|
self.assertIn("re-authorized", task.body)
|
|
self.assertNotIn("private-profile-id", repr(task))
|
|
self.assertNotIn("private.example.invalid", repr(task))
|
|
self.assertNotIn("secret-root", repr(task))
|
|
visible.assert_called_once()
|
|
self.assertEqual(("group-1",), tuple(visible.call_args.kwargs["group_ids"]))
|
|
|
|
without_authority = {
|
|
topic.id: topic
|
|
for topic in documentation_topics(self.context({"files:file:read"}))
|
|
}
|
|
self.assertNotIn("files.workflow.import-managed-snapshot", without_authority)
|
|
self.assertIn(
|
|
"both permission to view Files and permission to upload",
|
|
without_authority["files.connector-import-unavailable"].body,
|
|
)
|
|
|
|
def test_connector_limitation_is_precise_but_never_exposes_profile_internals(
|
|
self,
|
|
) -> None:
|
|
blocked = ConnectorProfile(
|
|
id="sensitive-profile-id",
|
|
label="Sensitive label",
|
|
provider="s3",
|
|
scope_type="tenant",
|
|
scope_id="tenant-1",
|
|
endpoint_url="https://internal.example.invalid/private",
|
|
base_path="/classified",
|
|
credential_mode="basic",
|
|
password_value="credential-secret",
|
|
)
|
|
with patch(
|
|
"govoplan_files.backend.documentation.visible_connector_profiles_for_actor",
|
|
return_value=[blocked],
|
|
):
|
|
topics = {
|
|
topic.id: topic
|
|
for topic in documentation_topics(
|
|
self.context({"files:file:read", "files:file:upload"})
|
|
)
|
|
}
|
|
|
|
self.assertNotIn("files.workflow.import-managed-snapshot", topics)
|
|
limitation = topics["files.connector-import-unavailable"]
|
|
self.assertEqual("available", limitation.layer)
|
|
self.assertIn("pinning-safe provider", limitation.body)
|
|
payload = repr(limitation)
|
|
for secret in (
|
|
"sensitive-profile-id",
|
|
"Sensitive label",
|
|
"internal.example.invalid",
|
|
"classified",
|
|
"credential-secret",
|
|
):
|
|
self.assertNotIn(secret, payload)
|
|
|
|
def test_files_admin_connector_groups_do_not_widen_campaign_membership(self) -> None:
|
|
usable = ConnectorProfile(
|
|
id="group-profile",
|
|
label="Group profile",
|
|
provider="webdav",
|
|
scope_type="group",
|
|
scope_id="admin-visible-group",
|
|
endpoint_url="https://files.example.invalid",
|
|
credential_mode="anonymous",
|
|
)
|
|
|
|
def groups_for_actor(_session, *, include_admin_groups, **_kwargs):
|
|
return ["member-group", "admin-visible-group"] if include_admin_groups else ["member-group"]
|
|
|
|
with (
|
|
patch(
|
|
"govoplan_files.backend.documentation.user_group_ids",
|
|
side_effect=groups_for_actor,
|
|
),
|
|
patch(
|
|
"govoplan_files.backend.documentation._campaign_visibility",
|
|
return_value=None,
|
|
) as campaign_visibility,
|
|
patch(
|
|
"govoplan_files.backend.documentation.visible_connector_profiles_for_actor",
|
|
return_value=[usable],
|
|
) as visible,
|
|
):
|
|
topics = {
|
|
topic.id: topic
|
|
for topic in documentation_topics(
|
|
self.context(
|
|
{
|
|
"files:file:read",
|
|
"files:file:upload",
|
|
"files:file:admin",
|
|
}
|
|
)
|
|
)
|
|
}
|
|
|
|
self.assertIn("files.workflow.import-managed-snapshot", topics)
|
|
self.assertEqual(
|
|
("member-group", "admin-visible-group"),
|
|
tuple(visible.call_args.kwargs["group_ids"]),
|
|
)
|
|
self.assertEqual(
|
|
("member-group",),
|
|
tuple(campaign_visibility.call_args.kwargs["group_ids"]),
|
|
)
|
|
|
|
def test_connector_evaluation_errors_fail_closed_without_error_details(
|
|
self,
|
|
) -> None:
|
|
with patch(
|
|
"govoplan_files.backend.documentation.visible_connector_profiles_for_actor",
|
|
side_effect=RuntimeError("private endpoint /root credential-id"),
|
|
):
|
|
topics = {
|
|
topic.id: topic
|
|
for topic in documentation_topics(
|
|
self.context({"files:file:read", "files:file:upload"})
|
|
)
|
|
}
|
|
|
|
self.assertNotIn("files.workflow.import-managed-snapshot", topics)
|
|
limitation = topics["files.connector-import-unavailable"]
|
|
self.assertIn("could not be safely evaluated", limitation.body)
|
|
self.assertNotIn("private endpoint", repr(limitation))
|
|
self.assertNotIn("credential-id", repr(limitation))
|
|
|
|
def test_runtime_provider_only_emits_user_documentation(self) -> None:
|
|
self.assertEqual(
|
|
(), documentation_topics(self.context(set(), documentation_type="admin"))
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|