test_device_scope.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. from __future__ import annotations
  2. from datetime import UTC, datetime
  3. from uuid import uuid4
  4. import pytest
  5. def _source(**overrides):
  6. from app.core.data_research.sources import IngestionSourceRecord
  7. values = {
  8. "uid": str(uuid4()),
  9. "source_type": "database",
  10. "name": "设备源",
  11. "config": {"database_type": "postgresql"},
  12. "permission_scope": {"departments": ["equipment"]},
  13. "status": "active",
  14. "created_by": "admin-1",
  15. "created_at": datetime(2026, 7, 29, tzinfo=UTC),
  16. "updated_at": datetime(2026, 7, 29, tzinfo=UTC),
  17. }
  18. values.update(overrides)
  19. return IngestionSourceRecord(**values)
  20. class Repository:
  21. def __init__(self, records=()):
  22. self.records = {record.uid: record for record in records}
  23. self.saved = []
  24. def get(self, uid):
  25. return self.records.get(uid)
  26. def save(self, record):
  27. self.records[record.uid] = record
  28. self.saved.append(record)
  29. return record
  30. def list_device_sources(self):
  31. return tuple(self.records.values())
  32. def test_device_source_scope_is_sorted_unique_and_preserves_other_keys():
  33. from app.core.knowledge.device_scope import DeviceSourceScopeService
  34. source = _source()
  35. repository = Repository((source,))
  36. service = DeviceSourceScopeService(
  37. repository,
  38. clock=lambda: datetime(2026, 7, 30, tzinfo=UTC),
  39. )
  40. domain_a = str(uuid4())
  41. domain_b = str(uuid4())
  42. updated = service.update(
  43. source.uid,
  44. {"business_domains": [domain_b, domain_a, domain_b]},
  45. actor_is_admin=True,
  46. )
  47. assert updated.permission_scope == {
  48. "departments": ["equipment"],
  49. "business_domains": sorted([domain_a, domain_b]),
  50. }
  51. assert repository.saved == [updated]
  52. assert updated.updated_at == datetime(2026, 7, 30, tzinfo=UTC)
  53. def test_device_source_scope_rejects_invalid_uuid_and_more_than_100_domains():
  54. from app.core.knowledge.device_scope import (
  55. DeviceSourceScopeInvalid,
  56. DeviceSourceScopeService,
  57. )
  58. source = _source()
  59. service = DeviceSourceScopeService(Repository((source,)))
  60. with pytest.raises(DeviceSourceScopeInvalid, match="UUID"):
  61. service.update(
  62. source.uid,
  63. {"business_domains": ["not-a-uuid"]},
  64. actor_is_admin=True,
  65. )
  66. with pytest.raises(DeviceSourceScopeInvalid, match="100"):
  67. service.update(
  68. source.uid,
  69. {"business_domains": [str(uuid4()) for _ in range(101)]},
  70. actor_is_admin=True,
  71. )
  72. def test_device_source_scope_missing_source_and_payload_are_rejected():
  73. from app.core.knowledge.device_scope import (
  74. DeviceSourceScopeInvalid,
  75. DeviceSourceScopeNotFound,
  76. DeviceSourceScopeService,
  77. )
  78. service = DeviceSourceScopeService(Repository())
  79. with pytest.raises(DeviceSourceScopeNotFound):
  80. service.update(
  81. str(uuid4()),
  82. {"business_domains": []},
  83. actor_is_admin=True,
  84. )
  85. with pytest.raises(DeviceSourceScopeInvalid, match="business_domains"):
  86. service.update(
  87. str(uuid4()),
  88. {},
  89. actor_is_admin=True,
  90. )
  91. def test_empty_scope_is_explicitly_admin_only_and_updates_require_admin():
  92. from app.core.knowledge.device_scope import (
  93. DeviceSourceScopeForbidden,
  94. DeviceSourceScopeService,
  95. source_scope_access,
  96. )
  97. source = _source(permission_scope={"business_domains": []})
  98. service = DeviceSourceScopeService(Repository((source,)))
  99. assert source_scope_access(source.permission_scope) == {
  100. "business_domains": (),
  101. "admin_only": True,
  102. }
  103. with pytest.raises(DeviceSourceScopeForbidden):
  104. service.update(
  105. source.uid,
  106. {"business_domains": [str(uuid4())]},
  107. actor_is_admin=False,
  108. )
  109. def test_list_exposes_scope_metadata_but_never_source_configuration():
  110. from app.core.knowledge.device_scope import DeviceSourceScopeService
  111. source = _source(
  112. config={"password": "must-not-leak"},
  113. permission_scope={"business_domains": [str(uuid4())]},
  114. )
  115. service = DeviceSourceScopeService(Repository((source,)))
  116. records = service.list()
  117. assert records[0]["uid"] == source.uid
  118. assert records[0]["business_domains"] == tuple(
  119. source.permission_scope["business_domains"]
  120. )
  121. assert "config" not in records[0]
  122. assert "permission_scope" not in records[0]