test_provider.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import io
  2. import os
  3. import boto3
  4. import pytest
  5. from botocore.exceptions import ClientError
  6. from moto import mock_aws
  7. from open_webui.storage import provider
  8. from gcp_storage_emulator.server import create_server
  9. from google.cloud import storage
  10. def mock_upload_dir(monkeypatch, tmp_path):
  11. """Fixture to monkey-patch the UPLOAD_DIR and create a temporary directory."""
  12. directory = tmp_path / "uploads"
  13. directory.mkdir()
  14. monkeypatch.setattr(provider, "UPLOAD_DIR", str(directory))
  15. return directory
  16. def test_imports():
  17. provider.StorageProvider
  18. provider.LocalStorageProvider
  19. provider.S3StorageProvider
  20. provider.GCSStorageProvider
  21. provider.Storage
  22. def test_get_storage_provider():
  23. Storage = provider.get_storage_provider("local")
  24. assert isinstance(Storage, provider.LocalStorageProvider)
  25. Storage = provider.get_storage_provider("s3")
  26. assert isinstance(Storage, provider.S3StorageProvider)
  27. Storage = provider.get_storage_provider("gcs")
  28. assert isinstance(Storage, provider.GCSStorageProvider)
  29. with pytest.raises(RuntimeError):
  30. provider.get_storage_provider("invalid")
  31. def test_class_instantiation():
  32. with pytest.raises(TypeError):
  33. provider.StorageProvider()
  34. with pytest.raises(TypeError):
  35. class Test(provider.StorageProvider):
  36. pass
  37. Test()
  38. provider.LocalStorageProvider()
  39. provider.S3StorageProvider()
  40. provider.GCSStorageProvider()
  41. class TestLocalStorageProvider:
  42. Storage = provider.LocalStorageProvider()
  43. file_content = b"test content"
  44. file_bytesio = io.BytesIO(file_content)
  45. filename = "test.txt"
  46. filename_extra = "test_exyta.txt"
  47. file_bytesio_empty = io.BytesIO()
  48. def test_upload_file(self, monkeypatch, tmp_path):
  49. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  50. contents, file_path = self.Storage.upload_file(self.file_bytesio, self.filename)
  51. assert (upload_dir / self.filename).exists()
  52. assert (upload_dir / self.filename).read_bytes() == self.file_content
  53. assert contents == self.file_content
  54. assert file_path == str(upload_dir / self.filename)
  55. with pytest.raises(ValueError):
  56. self.Storage.upload_file(self.file_bytesio_empty, self.filename)
  57. def test_get_file(self, monkeypatch, tmp_path):
  58. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  59. file_path = str(upload_dir / self.filename)
  60. file_path_return = self.Storage.get_file(file_path)
  61. assert file_path == file_path_return
  62. def test_delete_file(self, monkeypatch, tmp_path):
  63. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  64. (upload_dir / self.filename).write_bytes(self.file_content)
  65. assert (upload_dir / self.filename).exists()
  66. file_path = str(upload_dir / self.filename)
  67. self.Storage.delete_file(file_path)
  68. assert not (upload_dir / self.filename).exists()
  69. def test_delete_all_files(self, monkeypatch, tmp_path):
  70. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  71. (upload_dir / self.filename).write_bytes(self.file_content)
  72. (upload_dir / self.filename_extra).write_bytes(self.file_content)
  73. self.Storage.delete_all_files()
  74. assert not (upload_dir / self.filename).exists()
  75. assert not (upload_dir / self.filename_extra).exists()
  76. @mock_aws
  77. class TestS3StorageProvider:
  78. def __init__(self):
  79. self.Storage = provider.S3StorageProvider()
  80. self.Storage.bucket_name = "my-bucket"
  81. self.s3_client = boto3.resource("s3", region_name="us-east-1")
  82. self.file_content = b"test content"
  83. self.filename = "test.txt"
  84. self.filename_extra = "test_exyta.txt"
  85. self.file_bytesio_empty = io.BytesIO()
  86. super().__init__()
  87. def test_upload_file(self, monkeypatch, tmp_path):
  88. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  89. # S3 checks
  90. with pytest.raises(Exception):
  91. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
  92. self.s3_client.create_bucket(Bucket=self.Storage.bucket_name)
  93. contents, s3_file_path = self.Storage.upload_file(
  94. io.BytesIO(self.file_content), self.filename
  95. )
  96. object = self.s3_client.Object(self.Storage.bucket_name, self.filename)
  97. assert self.file_content == object.get()["Body"].read()
  98. # local checks
  99. assert (upload_dir / self.filename).exists()
  100. assert (upload_dir / self.filename).read_bytes() == self.file_content
  101. assert contents == self.file_content
  102. assert s3_file_path == "s3://" + self.Storage.bucket_name + "/" + self.filename
  103. with pytest.raises(ValueError):
  104. self.Storage.upload_file(self.file_bytesio_empty, self.filename)
  105. def test_get_file(self, monkeypatch, tmp_path):
  106. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  107. self.s3_client.create_bucket(Bucket=self.Storage.bucket_name)
  108. contents, s3_file_path = self.Storage.upload_file(
  109. io.BytesIO(self.file_content), self.filename
  110. )
  111. file_path = self.Storage.get_file(s3_file_path)
  112. assert file_path == str(upload_dir / self.filename)
  113. assert (upload_dir / self.filename).exists()
  114. def test_delete_file(self, monkeypatch, tmp_path):
  115. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  116. self.s3_client.create_bucket(Bucket=self.Storage.bucket_name)
  117. contents, s3_file_path = self.Storage.upload_file(
  118. io.BytesIO(self.file_content), self.filename
  119. )
  120. assert (upload_dir / self.filename).exists()
  121. self.Storage.delete_file(s3_file_path)
  122. assert not (upload_dir / self.filename).exists()
  123. with pytest.raises(ClientError) as exc:
  124. self.s3_client.Object(self.Storage.bucket_name, self.filename).load()
  125. error = exc.value.response["Error"]
  126. assert error["Code"] == "404"
  127. assert error["Message"] == "Not Found"
  128. def test_delete_all_files(self, monkeypatch, tmp_path):
  129. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  130. # create 2 files
  131. self.s3_client.create_bucket(Bucket=self.Storage.bucket_name)
  132. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
  133. object = self.s3_client.Object(self.Storage.bucket_name, self.filename)
  134. assert self.file_content == object.get()["Body"].read()
  135. assert (upload_dir / self.filename).exists()
  136. assert (upload_dir / self.filename).read_bytes() == self.file_content
  137. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename_extra)
  138. object = self.s3_client.Object(self.Storage.bucket_name, self.filename_extra)
  139. assert self.file_content == object.get()["Body"].read()
  140. assert (upload_dir / self.filename).exists()
  141. assert (upload_dir / self.filename).read_bytes() == self.file_content
  142. self.Storage.delete_all_files()
  143. assert not (upload_dir / self.filename).exists()
  144. with pytest.raises(ClientError) as exc:
  145. self.s3_client.Object(self.Storage.bucket_name, self.filename).load()
  146. error = exc.value.response["Error"]
  147. assert error["Code"] == "404"
  148. assert error["Message"] == "Not Found"
  149. assert not (upload_dir / self.filename_extra).exists()
  150. with pytest.raises(ClientError) as exc:
  151. self.s3_client.Object(self.Storage.bucket_name, self.filename_extra).load()
  152. error = exc.value.response["Error"]
  153. assert error["Code"] == "404"
  154. assert error["Message"] == "Not Found"
  155. self.Storage.delete_all_files()
  156. assert not (upload_dir / self.filename).exists()
  157. assert not (upload_dir / self.filename_extra).exists()
  158. class TestGCSStorageProvider:
  159. Storage = provider.GCSStorageProvider()
  160. Storage.bucket_name = "my-bucket"
  161. file_content = b"test content"
  162. filename = "test.txt"
  163. filename_extra = "test_exyta.txt"
  164. file_bytesio_empty = io.BytesIO()
  165. @pytest.fixture(scope="class")
  166. def setup(self):
  167. host, port = "localhost", 9023
  168. server = create_server(host, port, in_memory=True)
  169. server.start()
  170. os.environ["STORAGE_EMULATOR_HOST"] = f"http://{host}:{port}"
  171. gcs_client = storage.Client()
  172. bucket = gcs_client.bucket(self.Storage.bucket_name)
  173. bucket.create()
  174. self.Storage.gcs_client, self.Storage.bucket = gcs_client, bucket
  175. yield
  176. bucket.delete(force=True)
  177. server.stop()
  178. def test_upload_file(self, monkeypatch, tmp_path, setup):
  179. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  180. # catch error if bucket does not exist
  181. with pytest.raises(Exception):
  182. self.Storage.bucket = monkeypatch(self.Storage, "bucket", None)
  183. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
  184. contents, gcs_file_path = self.Storage.upload_file(
  185. io.BytesIO(self.file_content), self.filename
  186. )
  187. object = self.Storage.bucket.get_blob(self.filename)
  188. assert self.file_content == object.download_as_bytes()
  189. # local checks
  190. assert (upload_dir / self.filename).exists()
  191. assert (upload_dir / self.filename).read_bytes() == self.file_content
  192. assert contents == self.file_content
  193. assert gcs_file_path == "gs://" + self.Storage.bucket_name + "/" + self.filename
  194. # test error if file is empty
  195. with pytest.raises(ValueError):
  196. self.Storage.upload_file(self.file_bytesio_empty, self.filename)
  197. def test_get_file(self, monkeypatch, tmp_path, setup):
  198. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  199. contents, gcs_file_path = self.Storage.upload_file(
  200. io.BytesIO(self.file_content), self.filename
  201. )
  202. file_path = self.Storage.get_file(gcs_file_path)
  203. assert file_path == str(upload_dir / self.filename)
  204. assert (upload_dir / self.filename).exists()
  205. def test_delete_file(self, monkeypatch, tmp_path, setup):
  206. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  207. contents, gcs_file_path = self.Storage.upload_file(
  208. io.BytesIO(self.file_content), self.filename
  209. )
  210. # ensure that local directory has the uploaded file as well
  211. assert (upload_dir / self.filename).exists()
  212. assert self.Storage.bucket.get_blob(self.filename).name == self.filename
  213. self.Storage.delete_file(gcs_file_path)
  214. # check that deleting file from gcs will delete the local file as well
  215. assert not (upload_dir / self.filename).exists()
  216. assert self.Storage.bucket.get_blob(self.filename) == None
  217. def test_delete_all_files(self, monkeypatch, tmp_path, setup):
  218. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  219. # create 2 files
  220. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
  221. object = self.Storage.bucket.get_blob(self.filename)
  222. assert (upload_dir / self.filename).exists()
  223. assert (upload_dir / self.filename).read_bytes() == self.file_content
  224. assert self.Storage.bucket.get_blob(self.filename).name == self.filename
  225. assert self.file_content == object.download_as_bytes()
  226. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename_extra)
  227. object = self.Storage.bucket.get_blob(self.filename_extra)
  228. assert (upload_dir / self.filename_extra).exists()
  229. assert (upload_dir / self.filename_extra).read_bytes() == self.file_content
  230. assert self.Storage.bucket.get_blob(self.filename_extra).name == self.filename_extra
  231. assert self.file_content == object.download_as_bytes()
  232. self.Storage.delete_all_files()
  233. assert not (upload_dir / self.filename).exists()
  234. assert not (upload_dir / self.filename_extra).exists()
  235. assert self.Storage.bucket.get_blob(self.filename) == None
  236. assert self.Storage.bucket.get_blob(self.filename_extra) == None