"""Run: python3 -m unittest -v test_catalog_roundtrip.py. Uses disposable DBs only.""" import copy import json import tempfile import unittest from pathlib import Path import server from import_catalog import import_categories class CatalogTests(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() server.DATA_DIR = Path(self.tmp.name) server.DB_PATH = server.DATA_DIR / 'test.sqlite' server.DIST_CATALOG = server.DATA_DIR / 'public.json' self.admin = server.ensure_admin_user server.ensure_admin_user = lambda: None server.init_db() self.payload = {'metadata':{'eurToTomanRate':302700}, 'images':[{'id':'photo','original_url':'/i/photo','local_path':'media/photo.webp'}], 'categories':[{'slug':'lip-gloss','name':'Lip Gloss','nameFa':'لیپ گلاس','parentSlug':'lips'}], 'products':[{'id':'parent','slug':'lip-gloss','name':'Official English Name','nameFa':'لیپ گلاس کیکو', 'description':'توضیحات فارسی محصول', 'seoTitle':'عنوان سئو', 'seoDescription':'متای فارسی', 'details':[{'key':'finish','label':'فینیش','value':'براق'}], 'blocks':[{'type':'SPECIFICATIONS','content':{'custom':'preserved'}}], 'variants':[{'id':'shade01','slug':'lip-gloss-01','name':'Official English Name 01','colorCode':'01','colorName':'Rose', 'description':'توضیحات رنگ اول','seoTitle':'سئو رنگ اول','seoDescription':'متای رنگ اول', 'details':[{'key':'finish','label':'فینیش','value':'مات'}], 'price':123456,'stock':3,'images':['/media/a.webp','/media/b.webp'],'heroImage':'/media/a.webp'}, {'id':'shade02','slug':'lip-gloss-02','name':'Official English Name 02','shade':'02 Red', 'description':'توضیحات رنگ دوم','details':{'coverage':'full'},'price':234567,'stock':0}]}]} def tearDown(self): server.ensure_admin_user = self.admin self.tmp.cleanup() def read(self): with server.db() as conn: return server.product_dict(conn.execute('SELECT * FROM products').fetchone()) def test_roundtrip_and_public_export(self): import_categories(self.payload) result = server.import_products_payload(self.payload) self.assertEqual(result['created'],1) self.assertEqual(result['errors'],[]) first = self.read() self.assertEqual(len(first['colorVariants']),2) self.assertEqual(first['stock'],3) for key in ('description','seoTitle','seoDescription','details','blocks'): self.assertEqual(first[key],self.payload['products'][0][key]) for actual,expected in zip(first['colorVariants'],self.payload['products'][0]['variants']): for key,value in expected.items(): self.assertEqual(actual[key],value) server.export_catalog() public=json.loads(server.DIST_CATALOG.read_text()) self.assertEqual(public['products'][0],first) dest=server.DATA_DIR/'backup.json' server.export_import_bundle(dest) bundle=json.loads(dest.read_text()) server.DB_PATH=server.DATA_DIR/'second.sqlite' server.init_db() import_categories(bundle) server.import_products_payload(bundle) self.assertEqual(self.read(),first) with server.db() as conn: category=conn.execute('SELECT * FROM categories').fetchone() self.assertEqual(category['slug'],'lip-gloss') self.assertEqual(category['name_fa'],'لیپ گلاس') def test_reimport_idempotent_and_partial_parent(self): server.import_products_payload(self.payload) self.assertEqual(server.import_products_payload(self.payload)['updated'],1) server.import_products_payload({'products':[{'id':'parent','name':'Updated','slug':'lip-gloss'}]}) self.assertEqual(self.read()['description'],'توضیحات فارسی محصول') self.assertEqual(len(self.read()['colorVariants']),2) with server.db() as conn:self.assertEqual(conn.execute('SELECT COUNT(*) FROM products').fetchone()[0],1) def test_invalid_variants_do_not_replace_existing(self): server.import_products_payload(self.payload) before=self.read() bad=copy.deepcopy(self.payload) bad['products'][0]['variants'][1]['id']='shade01' self.assertEqual(server.import_products_payload(bad)['skipped'],1) self.assertEqual(self.read(),before) bad['products'][0]['variants'][1]['id']='shade02' bad['products'][0]['variants'][0]['price']=float('nan') self.assertEqual(server.import_products_payload(bad)['skipped'],1) self.assertEqual(self.read(),before) def test_inactive_retained_in_backup_not_public(self): self.payload['products'][0]['active']=False server.import_products_payload(self.payload) server.export_catalog() self.assertEqual(json.loads(server.DIST_CATALOG.read_text())['products'],[]) dest=server.DATA_DIR/'backup.json' server.export_import_bundle(dest) self.assertEqual(len(json.loads(dest.read_text())['products']),1) def test_old_database_migration(self): # Recreate the original product table by dropping only the newly added column. with server.db() as conn:conn.execute('ALTER TABLE products DROP COLUMN content_json') server.init_db() server.init_db() self.assertEqual(server.import_products_payload(self.payload)['created'],1) def test_image_batches_merge(self): server.import_products_payload(self.payload) server.import_products_payload({'metadata':{'newKey':'x'},'images':[{'id':'another','original_url':'/i/another'}],'products':[]}) dest=server.DATA_DIR/'backup.json' server.export_import_bundle(dest) data=json.loads(dest.read_text()) self.assertEqual(len(data['images']),2) self.assertEqual(data['metadata']['eurToTomanRate'],302700) if __name__=='__main__': unittest.main()