import assert from 'node:assert/strict'; import { mkdtempSync, existsSync, readFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import sharp from 'sharp'; import LibDashStore from '../src/index.js'; import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js'; async function runTests() { console.log('--- Starting SQLite file and pic directory storage tests ---'); const tempDir = mkdtempSync(join(tmpdir(), 'test-sqlite-store-')); const dbFile = join(tempDir, 'test.db'); const storageDir = join(tempDir, 'custom-storage'); const provider = new BetterSqlite3Provider({ filename: dbFile, storageDir, }); await provider.initialize(); const store = await new LibDashStore(provider); try { // Verify storage subdirectories exist assert(existsSync(join(storageDir, 'content')), 'content dir should exist'); assert(existsSync(join(storageDir, 'thumbavif')), 'thumbavif dir should exist'); assert(existsSync(join(storageDir, 'fullbinary')), 'fullbinary dir should exist'); assert(existsSync(join(storageDir, 'thumbbinary')), 'thumbbinary dir should exist'); // Verify sqlite tables do NOT contain blob columns const fileTableInfo = provider.db.pragma('table_info("file")'); const fileColNames = fileTableInfo.map((c) => c.name); console.log('file table columns:', fileColNames); assert(!fileColNames.includes('content'), 'file table should NOT have content column'); assert(!fileColNames.includes('thumbavif'), 'file table should NOT have thumbavif column'); const picTableInfo = provider.db.pragma('table_info("pic")'); const picColNames = picTableInfo.map((c) => c.name); console.log('pic table columns:', picColNames); assert(!picColNames.includes('fullbinary'), 'pic table should NOT have fullbinary column'); assert(!picColNames.includes('thumbbinary'), 'pic table should NOT have thumbbinary column'); // Setup base data: google_auth, company, unit, article, manifest const googleAuthId = await store.google_auth.add('test@example.com'); const company = await store.company.create({ name: 'Test Corp', iso639: 'de', taxrate: 19, reducedtaxrate: 7, googleAuthId, }); await store.google_auth.setSelectedCompany(googleAuthId, company.id); // 1. Test Manifest Attachment (file table) console.log('1. Testing manifestattachment with file table...'); const delivery = await store.delivery.create(googleAuthId, { externalNumber: 'DEL-1', date: new Date().toISOString(), lines: [ { name: 'Item A', amount: 5, net: 10, taxrate: 19, }, ], }); const manifestId = delivery.id; const testContent = Buffer.from('Hello SQLite Storage Engine! PDF or text content test.'); const testThumb = await sharp({ create: { width: 64, height: 64, channels: 4, background: { r: 255, g: 0, b: 0, alpha: 1 }, }, }) .avif() .toBuffer(); const attachment = await store.manifestattachment.add(googleAuthId, manifestId, { name: 'test-doc.txt', mimetype: 'text/plain', content: testContent, thumbavif: testThumb, thumbw: 64, thumbh: 64, }); console.log('Attachment created with hash:', attachment.hash); // Verify files on disk have correct extensions according to mimetype const contentPath = join(storageDir, 'content', attachment.hash + '.txt'); const thumbPath = join(storageDir, 'thumbavif', attachment.hash + '.avif'); assert(existsSync(contentPath), 'Content file must exist on disk at ' + contentPath); assert(existsSync(thumbPath), 'Thumb file must exist on disk at ' + thumbPath); assert.deepEqual(readFileSync(contentPath), testContent, 'Content on disk must match'); assert.deepEqual(readFileSync(thumbPath), testThumb, 'Thumb on disk must match'); // Verify getById const fetched = await store.manifestattachment.getById(googleAuthId, manifestId, attachment.id); assert.equal(fetched.name, 'test-doc.txt'); assert(Buffer.isBuffer(fetched.content), 'Fetched content must be a Buffer'); assert.deepEqual(fetched.content, testContent, 'Fetched content must match original'); assert(Buffer.isBuffer(fetched.thumbavif), 'Fetched thumbavif must be a Buffer'); assert.deepEqual(fetched.thumbavif, testThumb, 'Fetched thumbavif must match original'); // Verify listByManifestId const list = await store.manifestattachment.listByManifestId(googleAuthId, manifestId); assert.equal(list.length, 1); assert(Buffer.isBuffer(list[0].thumbavif), 'List thumbavif must be a Buffer'); assert.deepEqual(list[0].thumbavif, testThumb); // Verify remove await store.manifestattachment.remove(googleAuthId, manifestId, attachment.id); assert(!existsSync(contentPath), 'Content file should be deleted on remove'); assert(!existsSync(thumbPath), 'Thumb file should be deleted on remove'); // Test delivery creation with inline attached files console.log('1b. Testing delivery.create with inline attached file...'); const deliveryWithFile = await store.delivery.create(googleAuthId, { externalNumber: 'DEL-WITH-FILE', date: new Date().toISOString(), lines: [{ name: 'Item B', amount: 1, net: 5, taxrate: 19 }], files: [{ name: 'inline.txt', mimetype: 'text/plain', content: Buffer.from('Inline delivery file content'), }], }); assert(deliveryWithFile.files && deliveryWithFile.files.length === 1, 'Delivery should have 1 attached file'); const inlineHash = deliveryWithFile.files[0].hash; const inlineContentPath = join(storageDir, 'content', inlineHash + '.txt'); const inlineThumbPath = join(storageDir, 'thumbavif', inlineHash + '.avif'); assert(existsSync(inlineContentPath), 'Inline file content must exist on disk with .txt extension'); assert(existsSync(inlineThumbPath), 'Inline file thumb must exist on disk with .avif extension'); const fetchedDelivery = await store.delivery.getById(googleAuthId, deliveryWithFile.id); assert(fetchedDelivery.files && fetchedDelivery.files.length === 1); assert(fetchedDelivery.files[0].thumbavif, 'Fetched delivery file should include thumbavif'); console.log('Manifest attachment tests passed!'); // 2. Test Article Image (pic table) console.log('2. Testing article.addImage with pic table...'); const cat = await store.category.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'Sub Category', }); const article = await store.article.createUnderParent({ googleAuthId, parentId: cat.id, name: 'Test Article', }); const sampleImage = await sharp({ create: { width: 300, height: 200, channels: 4, background: { r: 0, g: 128, b: 255, alpha: 1 }, }, }) .png() .toBuffer(); const picResult = await store.article.addImage(googleAuthId, article.sku, sampleImage); const fullHash = picResult.fullHash; console.log('Article pic added with hash:', fullHash); const fullPath = join(storageDir, 'fullbinary', fullHash + '.avif'); const picThumbPath = join(storageDir, 'thumbbinary', fullHash + '.avif'); assert(existsSync(fullPath), 'Full picture must exist on disk at ' + fullPath); assert(existsSync(picThumbPath), 'Thumb picture must exist on disk at ' + picThumbPath); // Verify getImageByHash const fullPic = await store.pic.getImageByHash(googleAuthId, fullHash); assert(Buffer.isBuffer(fullPic.buffer), 'Image buffer must be returned'); assert.deepEqual(fullPic.buffer, readFileSync(fullPath)); // Verify getThumbByHash const thumbPic = await store.pic.getThumbByHash(googleAuthId, fullHash); assert(Buffer.isBuffer(thumbPic.buffer), 'Thumbnail buffer must be returned'); assert.deepEqual(thumbPic.buffer, readFileSync(picThumbPath)); // Verify deleteImage await store.article.deleteImage(googleAuthId, article.sku, fullHash); assert(!existsSync(fullPath), 'Full pic file should be deleted on deleteImage'); assert(!existsSync(picThumbPath), 'Thumb pic file should be deleted on deleteImage'); console.log('Article pic tests passed!'); // 3. Test purge console.log('3. Testing purge...'); // Add one more file before purge await store.manifestattachment.add(googleAuthId, manifestId, { name: 'purge-test.txt', mimetype: 'text/plain', content: Buffer.from('To be purged'), }); await provider.purge(); assert(!existsSync(contentPath), 'Files should be purged'); console.log('Purge tests passed!'); console.log('\nAll tests PASSED successfully!'); } finally { provider.close(); try { rmSync(tempDir, { recursive: true, force: true }); } catch { // ignore } } } runTests().catch((err) => { console.error('Test failed with error:', err); process.exit(1); });