test_provider.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  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. Storage = provider.S3StorageProvider()
  79. Storage.bucket_name = "my-bucket"
  80. s3_client = boto3.resource("s3", region_name="us-east-1")
  81. file_content = b"test content"
  82. filename = "test.txt"
  83. filename_extra = "test_exyta.txt"
  84. file_bytesio_empty = io.BytesIO()
  85. def test_upload_file(self, monkeypatch, tmp_path):
  86. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  87. # S3 checks
  88. with pytest.raises(Exception):
  89. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
  90. self.s3_client.create_bucket(Bucket=self.Storage.bucket_name)
  91. contents, s3_file_path = self.Storage.upload_file(
  92. io.BytesIO(self.file_content), self.filename
  93. )
  94. object = self.s3_client.Object(self.Storage.bucket_name, self.filename)
  95. assert self.file_content == object.get()["Body"].read()
  96. # local checks
  97. assert (upload_dir / self.filename).exists()
  98. assert (upload_dir / self.filename).read_bytes() == self.file_content
  99. assert contents == self.file_content
  100. assert s3_file_path == "s3://" + self.Storage.bucket_name + "/" + self.filename
  101. with pytest.raises(ValueError):
  102. self.Storage.upload_file(self.file_bytesio_empty, self.filename)
  103. def test_get_file(self, monkeypatch, tmp_path):
  104. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  105. self.s3_client.create_bucket(Bucket=self.Storage.bucket_name)
  106. contents, s3_file_path = self.Storage.upload_file(
  107. io.BytesIO(self.file_content), self.filename
  108. )
  109. file_path = self.Storage.get_file(s3_file_path)
  110. assert file_path == str(upload_dir / self.filename)
  111. assert (upload_dir / self.filename).exists()
  112. def test_delete_file(self, monkeypatch, tmp_path):
  113. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  114. self.s3_client.create_bucket(Bucket=self.Storage.bucket_name)
  115. contents, s3_file_path = self.Storage.upload_file(
  116. io.BytesIO(self.file_content), self.filename
  117. )
  118. assert (upload_dir / self.filename).exists()
  119. self.Storage.delete_file(s3_file_path)
  120. assert not (upload_dir / self.filename).exists()
  121. with pytest.raises(ClientError) as exc:
  122. self.s3_client.Object(self.Storage.bucket_name, self.filename).load()
  123. error = exc.value.response["Error"]
  124. assert error["Code"] == "404"
  125. assert error["Message"] == "Not Found"
  126. def test_delete_all_files(self, monkeypatch, tmp_path):
  127. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  128. # create 2 files
  129. self.s3_client.create_bucket(Bucket=self.Storage.bucket_name)
  130. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
  131. object = self.s3_client.Object(self.Storage.bucket_name, self.filename)
  132. assert self.file_content == object.get()["Body"].read()
  133. assert (upload_dir / self.filename).exists()
  134. assert (upload_dir / self.filename).read_bytes() == self.file_content
  135. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename_extra)
  136. object = self.s3_client.Object(self.Storage.bucket_name, self.filename_extra)
  137. assert self.file_content == object.get()["Body"].read()
  138. assert (upload_dir / self.filename).exists()
  139. assert (upload_dir / self.filename).read_bytes() == self.file_content
  140. self.Storage.delete_all_files()
  141. assert not (upload_dir / self.filename).exists()
  142. with pytest.raises(ClientError) as exc:
  143. self.s3_client.Object(self.Storage.bucket_name, self.filename).load()
  144. error = exc.value.response["Error"]
  145. assert error["Code"] == "404"
  146. assert error["Message"] == "Not Found"
  147. assert not (upload_dir / self.filename_extra).exists()
  148. with pytest.raises(ClientError) as exc:
  149. self.s3_client.Object(self.Storage.bucket_name, self.filename_extra).load()
  150. error = exc.value.response["Error"]
  151. assert error["Code"] == "404"
  152. assert error["Message"] == "Not Found"
  153. self.Storage.delete_all_files()
  154. assert not (upload_dir / self.filename).exists()
  155. assert not (upload_dir / self.filename_extra).exists()
  156. class TestGCSStorageProvider:
  157. Storage = provider.GCSStorageProvider()
  158. Storage.bucket_name = "my-bucket"
  159. file_content = b"test content"
  160. filename = "test.txt"
  161. filename_extra = "test_exyta.txt"
  162. file_bytesio_empty = io.BytesIO()
  163. @pytest.fixture
  164. def setup(self):
  165. host, port = "localhost", 9023
  166. server = create_server(host, port, in_memory=True)
  167. server.start()
  168. os.environ["STORAGE_EMULATOR_HOST"] = f"http://{host}:{port}"
  169. gcs_client = storage.Client()
  170. bucket = gcs_client.bucket(self.Storage.bucket_name)
  171. bucket.create()
  172. yield gcs_client, bucket
  173. bucket.delete(force=True)
  174. server.stop()
  175. def test_upload_file(self, monkeypatch, tmp_path, setup):
  176. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  177. # test error if bucket does not exist
  178. with pytest.raises(Exception):
  179. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
  180. # creates bucket and test upload_file method, downloads the file and confirms contents
  181. self.Storage.gcs_client, self.Storage.bucket = setup
  182. contents, gcs_file_path = self.Storage.upload_file(
  183. io.BytesIO(self.file_content), self.filename
  184. )
  185. object = self.Storage.bucket.get_blob(self.filename)
  186. assert self.file_content == object.download_as_bytes()
  187. # local checks
  188. assert (upload_dir / self.filename).exists()
  189. assert (upload_dir / self.filename).read_bytes() == self.file_content
  190. assert contents == self.file_content
  191. assert gcs_file_path == "gs://" + self.Storage.bucket_name + "/" + self.filename
  192. # test error if file is empty
  193. with pytest.raises(ValueError):
  194. self.Storage.upload_file(self.file_bytesio_empty, self.filename)
  195. def test_get_file(self, monkeypatch, tmp_path, setup):
  196. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  197. self.Storage.gcs_client, self.Storage.bucket = setup
  198. contents, gcs_file_path = self.Storage.upload_file(
  199. io.BytesIO(self.file_content), self.filename
  200. )
  201. file_path = self.Storage.get_file(gcs_file_path)
  202. assert file_path == str(upload_dir / self.filename)
  203. assert (upload_dir / self.filename).exists()
  204. def test_delete_file(self, monkeypatch, tmp_path, setup):
  205. upload_dir = mock_upload_dir(monkeypatch, tmp_path)
  206. self.Storage.gcs_client, self.Storage.bucket = setup
  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. self.Storage.gcs_client, self.Storage.bucket = setup
  220. # create 2 files
  221. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
  222. object = self.Storage.bucket.get_blob(self.filename)
  223. assert (upload_dir / self.filename).exists()
  224. assert (upload_dir / self.filename).read_bytes() == self.file_content
  225. assert self.Storage.bucket.get_blob(self.filename).name == self.filename
  226. assert self.file_content == object.download_as_bytes()
  227. self.Storage.upload_file(io.BytesIO(self.file_content), self.filename_extra)
  228. object = self.Storage.bucket.get_blob(self.filename_extra)
  229. assert (upload_dir / self.filename_extra).exists()
  230. assert (upload_dir / self.filename_extra).read_bytes() == self.file_content
  231. assert self.Storage.bucket.get_blob(self.filename_extra).name == self.filename_extra
  232. assert self.file_content == object.download_as_bytes()
  233. self.Storage.delete_all_files()
  234. assert not (upload_dir / self.filename).exists()
  235. assert not (upload_dir / self.filename_extra).exists()
  236. assert self.Storage.bucket.get_blob(self.filename) == None
  237. assert self.Storage.bucket.get_blob(self.filename_extra) == None