import os import pathlib import tempfile from django.test import TestCase import settings from troggle.parsers import drawings from troggle.core.models.survex import DrawingFile from troggle.core.models.wallets import Wallet from troggle.core.models.survex import SingleScan from troggle.core.models.troggle import DataIssue class DrawingsPathlibTests(TestCase): def test_load_drawings_creates_expected_entries(self): with tempfile.TemporaryDirectory() as td: # create a small tree p = pathlib.Path(td) (p / 'one.pdf').write_text('pdf') (p / 'two.txt').write_text('txt') sub = p / 'dir' sub.mkdir() (sub / 'three.png').write_text('png') sub2 = p / 'dir2' sub2.mkdir() (sub2 / 'abc.th2').write_text('th2') (sub2 / 'abc.th').write_text('th') # point the module at our tempdir settings.DRAWINGS_DATA = td drawings.load_drawings_files() # all files should be present self.assertTrue(DrawingFile.objects.filter(dwgpath='one.pdf').exists()) self.assertTrue(DrawingFile.objects.filter(dwgpath='two.txt').exists()) self.assertTrue(DrawingFile.objects.filter(dwgpath='dir/three.png').exists()) self.assertTrue(DrawingFile.objects.filter(dwgpath='dir2/abc.th2').exists()) self.assertTrue(DrawingFile.objects.filter(dwgpath='dir2/abc.th').exists()) def test_hidden_and_backup_skipped(self): with tempfile.TemporaryDirectory() as td: p = pathlib.Path(td) (p / '.hidden').write_text('hid') (p / 'file~').write_text('bak') settings.DRAWINGS_DATA = td drawings.load_drawings_files() # Should not import hidden or backup files self.assertFalse(DrawingFile.objects.filter(dwgpath='.hidden').exists()) self.assertFalse(DrawingFile.objects.filter(dwgpath='file~').exists()) def test_no_extension_file(self): with tempfile.TemporaryDirectory() as td: p = pathlib.Path(td) (p / 'noext').write_text('data') settings.DRAWINGS_DATA = td drawings.load_drawings_files() self.assertTrue(DrawingFile.objects.filter(dwgpath='noext').exists()) def test_git_dir_skipped(self): with tempfile.TemporaryDirectory() as td: p = pathlib.Path(td) g = p / '.git' g.mkdir() (g / 'secret.txt').write_text('top secret') settings.DRAWINGS_DATA = td drawings.load_drawings_files() self.assertFalse(DrawingFile.objects.filter(dwgpath='.git/secret.txt').exists()) def test_bulk_create_chunks(self): # Create more than chunk size files to ensure bulk_create is called in multiple chunks count = 800 with tempfile.TemporaryDirectory() as td: p = pathlib.Path(td) for i in range(count): (p / f'file{i}.txt').write_text('x') settings.DRAWINGS_DATA = td drawings.load_drawings_files() self.assertEqual(DrawingFile.objects.count(), count) def test_parse_tunnel_links_wallet_and_scan(self): # Create a wallet and a singlescan, then ensure parse_tnl_file links them w = Wallet.objects.create(fpath='x', walletname='2025#20') ss = SingleScan.objects.create(ffile='x', name='notes.jpg', wallet=w) df = DrawingFile.objects.create(dwgpath='tst.th', dwgname='tst') drawings.parse_tnl_file(df, '2025#20/notes.jpg') self.assertIn(w, df.dwgwallets.all()) self.assertIn(ss, df.scans.all()) def test_drawing_reference_multiple_creates_dataissue(self): df1 = DrawingFile.objects.create(dwgpath='ref1', dwgname='shared') df2 = DrawingFile.objects.create(dwgpath='ref2', dwgname='shared') dfmain = DrawingFile.objects.create(dwgpath='main', dwgname='main') drawings.parse_tnl_file(dfmain, 'shared') di = DataIssue.objects.filter(parser='Tunnel', message__contains="files named 'shared'") self.assertTrue(di.exists()) def test_drawing_reference_single_no_dataissue(self): DrawingFile.objects.create(dwgpath='ref3', dwgname='unique') dfmain = DrawingFile.objects.create(dwgpath='main2', dwgname='main2') drawings.parse_tnl_file(dfmain, 'unique') di = DataIssue.objects.filter(parser='Tunnel', message__contains="files named 'unique'") self.assertFalse(di.exists()) def test_extension_helpers_and_constants(self): # Helpers should recognise supported/image suffixes (case-insensitive) self.assertTrue(drawings._is_supported_suffix('.png')) self.assertTrue(drawings._is_supported_suffix('.xml')) self.assertTrue(drawings._is_supported_suffix('.TH')) self.assertFalse(drawings._is_supported_suffix('')) self.assertFalse(drawings._is_supported_suffix('.exe')) self.assertTrue(drawings._is_image_suffix('.png')) self.assertTrue(drawings._is_image_suffix('.JPEG')) self.assertFalse(drawings._is_image_suffix('.xml')) self.assertFalse(drawings._is_image_suffix('')) # Constants should include expected values and be consistent self.assertIn('.png', drawings.IMAGE_EXTS) self.assertEqual(set(drawings.IMAGE_LIKE_EXTS), set(drawings.IMAGE_EXTS)) self.assertIn('.th', drawings.SUPPORTED_EXTENSIONS) self.assertIn('.png', drawings.SUPPORTED_EXTENSIONS) def test_fetch_drawingfiles_by_paths_chunks(self): # Create more items than typical SQLite parameter limit to ensure chunking count = 1200 rel_paths = [] objs = [] for i in range(count): rel = f'bigdir/file{i}.txt' rel_paths.append(rel) objs.append(DrawingFile(dwgpath=rel, dwgname=f'name{i}')) # Bulk create them efficiently DrawingFile.objects.bulk_create(objs) mapping = drawings.fetch_drawingfiles_by_paths(rel_paths, chunk_size=500) self.assertEqual(len(mapping), count) # Spot-check a few entries self.assertIn('bigdir/file0.txt', mapping) self.assertIn(f'bigdir/file{count-1}.txt', mapping) def test_assign_wallets_for_model_assigns_and_returns_wallets(self): w = Wallet.objects.create(fpath='x', walletname='2025#20') df = DrawingFile.objects.create(dwgpath='assign.th', dwgname='assign') res = drawings._assign_wallets_for_model(df, '2025#20', parser_label='AssignTest') self.assertTrue(res) self.assertIn(w, df.dwgwallets.all()) def test_assign_wallets_for_model_creates_dataissue_on_missing(self): df = DrawingFile.objects.create(dwgpath='missing.th', dwgname='missing') drawings._assign_wallets_for_model(df, 'NONEXISTENT', parser_label='AssignMissing') di = DataIssue.objects.filter(parser='AssignMissing', message__contains='not found') self.assertTrue(di.exists()) def test_assign_wallets_for_model_records_dataissue_on_exception(self): # Patch Wallet.objects.filter to raise an exception from unittest.mock import patch df = DrawingFile.objects.create(dwgpath='err.th', dwgname='err') with patch('troggle.core.models.wallets.Wallet.objects.filter') as mock_filter: mock_filter.side_effect = RuntimeError('boom') drawings._assign_wallets_for_model(df, 'WHATEVER', parser_label='AssignError') di = DataIssue.objects.filter(parser='AssignError', message__contains='Exception') self.assertTrue(di.exists())