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