test_file_policy.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. from __future__ import annotations
  2. import pytest
  3. def test_extension_mime_and_magic_must_agree():
  4. from app.core.data_research.file_policy import FilePolicy, FilePolicyViolation
  5. policy = FilePolicy(max_bytes=1024)
  6. result = policy.validate("definition.pdf", "application/pdf", b"%PDF-1.7\nbody")
  7. assert result.extension == ".pdf"
  8. assert result.media_type == "application/pdf"
  9. with pytest.raises(FilePolicyViolation, match="file signature"):
  10. policy.validate("definition.pdf", "application/pdf", b"not-a-pdf")
  11. with pytest.raises(FilePolicyViolation, match="media type"):
  12. policy.validate("definition.csv", "image/png", b"id,name\n1,A")
  13. def test_legacy_doc_unsafe_names_and_size_are_rejected():
  14. from app.core.data_research.file_policy import FilePolicy, FilePolicyViolation
  15. policy = FilePolicy(max_bytes=8)
  16. with pytest.raises(FilePolicyViolation, match="convert.*DOCX"):
  17. policy.validate("legacy.doc", "application/msword", b"1234")
  18. with pytest.raises(FilePolicyViolation, match="unsafe filename"):
  19. policy.validate("../secret.csv", "text/csv", b"id\n1")
  20. with pytest.raises(FilePolicyViolation, match="size limit"):
  21. policy.validate("large.csv", "text/csv", b"id\n123456")
  22. class ArtifactRepository:
  23. def __init__(self):
  24. self.by_key = {}
  25. self.saved = []
  26. def find(self, source_uid, content_hash, parser_version):
  27. return self.by_key.get((source_uid, content_hash, parser_version))
  28. def save(self, artifact):
  29. key = (artifact.source_uid, artifact.content_hash, artifact.parser_version)
  30. self.by_key[key] = artifact
  31. self.saved.append(artifact)
  32. return artifact
  33. class Storage:
  34. def __init__(self):
  35. self.puts = []
  36. def put(self, object_key, content, media_type):
  37. self.puts.append((object_key, content, media_type))
  38. return f"minio://data-research/{object_key}"
  39. def test_artifact_storage_reuses_source_hash_and_hides_unsafe_filename():
  40. from app.core.data_research.artifacts import ArtifactService
  41. from app.core.data_research.file_policy import FilePolicy
  42. repository = ArtifactRepository()
  43. storage = Storage()
  44. service = ArtifactService(
  45. repository,
  46. storage,
  47. FilePolicy(max_bytes=1024),
  48. uid_factory=lambda: "artifact-1",
  49. )
  50. first, created = service.store(
  51. "source-1", "客户定义.csv", "text/csv", b"id,name\n1,A", "csv-v1"
  52. )
  53. second, created_again = service.store(
  54. "source-1", "客户定义.csv", "text/csv", b"id,name\n1,A", "csv-v1"
  55. )
  56. assert created is True and created_again is False
  57. assert first == second
  58. assert len(storage.puts) == 1
  59. assert "客户定义.csv" not in first.storage_ref
  60. assert first.content_hash in first.storage_ref