test_vlm_image_inputs.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. """Offline tests for the unified VLM image_inputs path."""
  2. from __future__ import annotations
  3. import base64
  4. import hashlib
  5. from typing import Any
  6. import pytest
  7. from lightrag.llm._vision_utils import (
  8. image_audit_metadata,
  9. image_cache_metadata,
  10. normalize_image_inputs,
  11. )
  12. pytestmark = pytest.mark.offline
  13. PNG_BYTES = (
  14. b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
  15. b"\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\rIDATx\x9cc\xf8"
  16. b"\xcf\xc0\x00\x00\x00\x03\x00\x01\x5c\xcc\xd9\x9e\x00\x00\x00\x00"
  17. b"IEND\xaeB`\x82"
  18. )
  19. JPEG_BYTES = b"\xff\xd8\xff\xe0\x00\x10JFIF" + b"\x00" * 16
  20. def _b64(raw: bytes) -> str:
  21. return base64.b64encode(raw).decode("ascii")
  22. def test_normalize_accepts_raw_base64_and_detects_png():
  23. result = normalize_image_inputs([_b64(PNG_BYTES)])
  24. assert len(result) == 1
  25. img = result[0]
  26. assert img.index == 0
  27. assert img.mime_type == "image/png"
  28. assert img.raw_bytes == PNG_BYTES
  29. assert img.sha256 == hashlib.sha256(PNG_BYTES).hexdigest()
  30. assert img.source_id is None
  31. assert img.source_file is None
  32. def test_normalize_accepts_data_url_and_uses_declared_mime():
  33. data_url = f"data:image/jpeg;base64,{_b64(JPEG_BYTES)}"
  34. result = normalize_image_inputs([data_url])
  35. assert len(result) == 1
  36. assert result[0].mime_type == "image/jpeg"
  37. def test_normalize_accepts_dict_with_metadata():
  38. dict_item: dict[str, Any] = {
  39. "base64": _b64(PNG_BYTES),
  40. "mime_type": "image/png",
  41. "source_id": "img-001",
  42. "source_file": "/tmp/foo.png",
  43. "modality": "image",
  44. "doc_id": "doc-1",
  45. }
  46. [img] = normalize_image_inputs([dict_item])
  47. assert img.source_id == "img-001"
  48. assert img.source_file == "/tmp/foo.png"
  49. assert img.modality == "image"
  50. assert img.doc_id == "doc-1"
  51. def test_normalize_empty_returns_empty_list():
  52. assert normalize_image_inputs(None) == []
  53. assert normalize_image_inputs([]) == []
  54. def test_normalize_rejects_invalid_base64():
  55. with pytest.raises(ValueError):
  56. normalize_image_inputs(["this is not base64@@@!!"])
  57. def test_normalize_rejects_unsupported_element_type():
  58. with pytest.raises(TypeError):
  59. normalize_image_inputs([12345])
  60. def test_normalize_rejects_dict_without_base64():
  61. with pytest.raises(ValueError):
  62. normalize_image_inputs([{"mime_type": "image/png"}])
  63. def test_cache_metadata_excludes_source_identifiers():
  64. images = normalize_image_inputs(
  65. [
  66. {
  67. "base64": _b64(PNG_BYTES),
  68. "source_id": "leak-id",
  69. "source_file": "/leak/path.png",
  70. }
  71. ]
  72. )
  73. [meta] = image_cache_metadata(images)
  74. assert "source_id" not in meta
  75. assert "source_file" not in meta
  76. assert meta["sha256"] == hashlib.sha256(PNG_BYTES).hexdigest()
  77. assert meta["mime_type"] == "image/png"
  78. assert meta["bytes"] == len(PNG_BYTES)
  79. def test_cache_metadata_same_image_different_filename_is_identical():
  80. img_a = normalize_image_inputs(
  81. [{"base64": _b64(PNG_BYTES), "source_file": "/a/x.png"}]
  82. )
  83. img_b = normalize_image_inputs(
  84. [{"base64": _b64(PNG_BYTES), "source_file": "/b/y.png"}]
  85. )
  86. assert image_cache_metadata(img_a) == image_cache_metadata(img_b)
  87. def test_audit_metadata_includes_full_provenance_without_raw_base64():
  88. images = normalize_image_inputs(
  89. [
  90. {
  91. "base64": _b64(PNG_BYTES),
  92. "source_id": "img-001",
  93. "source_file": "/tmp/foo.png",
  94. "modality": "image",
  95. "doc_id": "doc-1",
  96. }
  97. ]
  98. )
  99. [audit] = image_audit_metadata(images)
  100. assert audit["source_id"] == "img-001"
  101. assert audit["source_file"] == "/tmp/foo.png"
  102. assert audit["sha256"] == hashlib.sha256(PNG_BYTES).hexdigest()
  103. # The audit blob must never re-leak the raw base64 payload.
  104. assert "base64" not in audit
  105. assert _b64(PNG_BYTES) not in str(audit)