import datetime import gzip import os import shutil import tempfile from unittest import mock from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase, override_settings from django.utils import timezone from bookmarks.models import BookmarkAsset from bookmarks.services import assets from bookmarks.tests.helpers import BookmarkFactoryMixin, disable_logging class AssetServiceTestCase(TestCase, BookmarkFactoryMixin): def setUp(self) -> None: self.get_or_create_test_user() self.temp_dir = tempfile.mkdtemp() self.settings_override = override_settings(LD_ASSET_FOLDER=self.temp_dir) self.settings_override.enable() self.html_content = "

Hello, World!

" self.mock_singlefile_create_snapshot_patcher = mock.patch( "bookmarks.services.singlefile.create_snapshot", ) self.mock_singlefile_create_snapshot = ( self.mock_singlefile_create_snapshot_patcher.start() ) self.mock_singlefile_create_snapshot.side_effect = lambda url, filepath: ( open(filepath, "w").write(self.html_content) ) def tearDown(self) -> None: shutil.rmtree(self.temp_dir) self.mock_singlefile_create_snapshot_patcher.stop() def get_saved_snapshot_file(self): # look up first file in the asset folder files = os.listdir(self.temp_dir) if files: return files[0] def test_create_snapshot_asset(self): bookmark = self.setup_bookmark() asset = assets.create_snapshot_asset(bookmark) self.assertIsNotNone(asset) self.assertEqual(asset.bookmark, bookmark) self.assertEqual(asset.asset_type, BookmarkAsset.TYPE_SNAPSHOT) self.assertEqual(asset.content_type, BookmarkAsset.CONTENT_TYPE_HTML) self.assertIn("HTML snapshot from", asset.display_name) self.assertEqual(asset.status, BookmarkAsset.STATUS_PENDING) # asset is not saved to the database self.assertIsNone(asset.id) def test_create_snapshot(self): bookmark = self.setup_bookmark(url="https://example.com") asset = assets.create_snapshot_asset(bookmark) asset.save() asset.date_created = timezone.datetime( 2023, 8, 11, 21, 45, 11, tzinfo=datetime.timezone.utc ) assets.create_snapshot(asset) expected_temp_filename = "snapshot_2023-08-11_214511_https___example.com.tmp" expected_temp_filepath = os.path.join(self.temp_dir, expected_temp_filename) expected_filename = "snapshot_2023-08-11_214511_https___example.com.html.gz" expected_filepath = os.path.join(self.temp_dir, expected_filename) # should call singlefile.create_snapshot with the correct arguments self.mock_singlefile_create_snapshot.assert_called_once_with( "https://example.com", expected_temp_filepath, ) # should create gzip file in asset folder self.assertTrue(os.path.exists(expected_filepath)) # gzip file should contain the correct content with gzip.open(expected_filepath, "rb") as gz_file: self.assertEqual(gz_file.read().decode(), self.html_content) # should remove temporary file self.assertFalse(os.path.exists(expected_temp_filepath)) # should update asset status and file asset.refresh_from_db() self.assertEqual(asset.status, BookmarkAsset.STATUS_COMPLETE) self.assertEqual(asset.file, expected_filename) self.assertTrue(asset.gzip) def test_create_snapshot_failure(self): bookmark = self.setup_bookmark(url="https://example.com") asset = assets.create_snapshot_asset(bookmark) asset.save() self.mock_singlefile_create_snapshot.side_effect = Exception with self.assertRaises(Exception): assets.create_snapshot(asset) asset.refresh_from_db() self.assertEqual(asset.status, BookmarkAsset.STATUS_FAILURE) def test_create_snapshot_truncates_asset_file_name(self): # Create a bookmark with a very long URL long_url = "http://" + "a" * 300 + ".com" bookmark = self.setup_bookmark(url=long_url) asset = assets.create_snapshot_asset(bookmark) asset.save() assets.create_snapshot(asset) saved_file = self.get_saved_snapshot_file() self.assertEqual(192, len(saved_file)) self.assertTrue(saved_file.startswith("snapshot_")) self.assertTrue(saved_file.endswith("aaaa.html.gz")) def test_upload_snapshot(self): bookmark = self.setup_bookmark(url="https://example.com") asset = assets.upload_snapshot(bookmark, self.html_content.encode()) # should create gzip file in asset folder saved_file_name = self.get_saved_snapshot_file() self.assertIsNotNone(saved_file_name) # verify file name self.assertTrue(saved_file_name.startswith("snapshot_")) self.assertTrue(saved_file_name.endswith("_https___example.com.html.gz")) # gzip file should contain the correct content with gzip.open(os.path.join(self.temp_dir, saved_file_name), "rb") as gz_file: self.assertEqual(gz_file.read().decode(), self.html_content) # should create asset self.assertIsNotNone(asset.id) self.assertEqual(asset.bookmark, bookmark) self.assertEqual(asset.asset_type, BookmarkAsset.TYPE_SNAPSHOT) self.assertEqual(asset.content_type, BookmarkAsset.CONTENT_TYPE_HTML) self.assertIn("HTML snapshot from", asset.display_name) self.assertEqual(asset.status, BookmarkAsset.STATUS_COMPLETE) self.assertEqual(asset.file, saved_file_name) self.assertTrue(asset.gzip) def test_upload_snapshot_failure(self): bookmark = self.setup_bookmark(url="https://example.com") # make gzip.open raise an exception with mock.patch("gzip.open") as mock_gzip_open: mock_gzip_open.side_effect = Exception with self.assertRaises(Exception): assets.upload_snapshot(bookmark, b"invalid content") # asset is not saved to the database self.assertIsNone(BookmarkAsset.objects.first()) def test_upload_snapshot_truncates_asset_file_name(self): # Create a bookmark with a very long URL long_url = "http://" + "a" * 300 + ".com" bookmark = self.setup_bookmark(url=long_url) assets.upload_snapshot(bookmark, self.html_content.encode()) saved_file = self.get_saved_snapshot_file() self.assertEqual(192, len(saved_file)) self.assertTrue(saved_file.startswith("snapshot_")) self.assertTrue(saved_file.endswith("aaaa.html.gz")) @disable_logging def test_upload_asset(self): bookmark = self.setup_bookmark() file_content = b"test content" upload_file = SimpleUploadedFile( "test_file.txt", file_content, content_type="text/plain" ) asset = assets.upload_asset(bookmark, upload_file) # should create file in asset folder saved_file_name = self.get_saved_snapshot_file() self.assertIsNotNone(upload_file) # verify file name self.assertTrue(saved_file_name.startswith("upload_")) self.assertTrue(saved_file_name.endswith("_test_file.txt")) # file should contain the correct content with open(os.path.join(self.temp_dir, saved_file_name), "rb") as file: self.assertEqual(file.read(), file_content) # should create asset self.assertIsNotNone(asset.id) self.assertEqual(asset.bookmark, bookmark) self.assertEqual(asset.asset_type, BookmarkAsset.TYPE_UPLOAD) self.assertEqual(asset.content_type, upload_file.content_type) self.assertEqual(asset.display_name, upload_file.name) self.assertEqual(asset.status, BookmarkAsset.STATUS_COMPLETE) self.assertEqual(asset.file, saved_file_name) self.assertEqual(asset.file_size, len(file_content)) self.assertFalse(asset.gzip) @disable_logging def test_upload_asset_truncates_asset_file_name(self): # Create a bookmark with a very long URL long_file_name = "a" * 300 + ".txt" bookmark = self.setup_bookmark() file_content = b"test content" upload_file = SimpleUploadedFile( long_file_name, file_content, content_type="text/plain" ) assets.upload_asset(bookmark, upload_file) saved_file = self.get_saved_snapshot_file() self.assertEqual(192, len(saved_file)) self.assertTrue(saved_file.startswith("upload_")) self.assertTrue(saved_file.endswith("aaaa.txt")) @disable_logging def test_upload_asset_failure(self): bookmark = self.setup_bookmark() upload_file = SimpleUploadedFile("test_file.txt", b"test content") # make open raise an exception with mock.patch("builtins.open") as mock_open: mock_open.side_effect = Exception with self.assertRaises(Exception): assets.upload_asset(bookmark, upload_file) # asset is not saved to the database self.assertIsNone(BookmarkAsset.objects.first())