291 lines
13 KiB
JavaScript
291 lines
13 KiB
JavaScript
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');
|
|
|
|
// Large file and image blobs stay on disk; the 32px small-thumb stays inline in pic.
|
|
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');
|
|
assert(picColNames.includes('smallthumbw'), 'pic table should have smallthumbw column');
|
|
assert(picColNames.includes('smallthumbh'), 'pic table should have smallthumbh column');
|
|
assert(picColNames.includes('smallthumbbinary'), 'pic table should have smallthumbbinary 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',
|
|
});
|
|
assert.equal(article.track_stock, true, 'Default track_stock must be true');
|
|
|
|
const fetchedArt = await store.article.getBySku(googleAuthId, article.sku);
|
|
assert.equal(fetchedArt.article.track_stock, true, 'getBySku must return track_stock: true');
|
|
|
|
await store.article.setTrackStockBySku(googleAuthId, article.sku, false);
|
|
const afterSet = await store.article.getBySku(googleAuthId, article.sku);
|
|
assert.equal(afterSet.article.track_stock, false, 'setTrackStockBySku false must update track_stock');
|
|
|
|
await store.article.updateBySku(googleAuthId, article.sku, { track_stock: true });
|
|
const afterUpdate = await store.article.getBySku(googleAuthId, article.sku);
|
|
assert.equal(afterUpdate.article.track_stock, true, 'updateBySku track_stock: true must update track_stock');
|
|
|
|
const history = await store.article.getHistoryBySku(googleAuthId, article.sku);
|
|
assert(history.some((h) => h.text.includes('Bestandsführung geändert von "Nein" auf "Ja"')));
|
|
|
|
const nonTracked = await store.article.createUnderParent({
|
|
googleAuthId,
|
|
parentId: cat.id,
|
|
name: 'Service Item',
|
|
track_stock: false,
|
|
});
|
|
assert.equal(nonTracked.track_stock, false, 'Explicit track_stock: false must be preserved');
|
|
const fetchedNonTracked = await store.article.getBySku(googleAuthId, nonTracked.sku);
|
|
assert.equal(fetchedNonTracked.article.track_stock, false);
|
|
|
|
const exported = await store.article.listExport(googleAuthId, cat.id);
|
|
const foundExport = exported.find((a) => a.sku === article.sku);
|
|
assert.equal(foundExport.track_stock, true, 'listExport must include track_stock');
|
|
|
|
console.log('Article track_stock tests passed!');
|
|
|
|
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;
|
|
assert.equal(picResult.smallThumb.w, 40);
|
|
assert.equal(picResult.smallThumb.h, 27);
|
|
const smallThumbRow = provider.db.prepare(
|
|
'SELECT smallthumbw, smallthumbh, smallthumbbinary FROM pic WHERE hash = ?',
|
|
).get(fullHash);
|
|
assert.equal(smallThumbRow.smallthumbw, 40);
|
|
assert.equal(smallThumbRow.smallthumbh, 27);
|
|
assert(Buffer.isBuffer(smallThumbRow.smallthumbbinary), 'Small thumb AVIF must be stored in the pic row');
|
|
const smallThumbMetadata = await sharp(smallThumbRow.smallthumbbinary).metadata();
|
|
assert.equal(smallThumbMetadata.format, 'heif');
|
|
assert.equal(smallThumbMetadata.width, 40);
|
|
assert.equal(smallThumbMetadata.height, 27);
|
|
|
|
const brand = await store.brand.create(googleAuthId, { name: 'Small Thumb Test Brand' });
|
|
await store.brand.addImage(googleAuthId, brand.id, sampleImage);
|
|
const listedBrands = await store.brand.list(googleAuthId, { offset: 0, limit: 100 });
|
|
const listedBrand = listedBrands.brands.find((item) => item.id === brand.id);
|
|
assert(listedBrand, 'Brand list should include the created brand');
|
|
assert.equal(listedBrand.pics.length, 1);
|
|
assert(!('smallThumbW' in listedBrand.pics[0]), 'Small-thumb fields should be omitted by default');
|
|
|
|
const listedBrandsWithSmallThumbs = await store.brand.list(googleAuthId, {
|
|
offset: 0,
|
|
limit: 100,
|
|
includeSmallThumbs: true,
|
|
});
|
|
const listedBrandWithSmallThumbs = listedBrandsWithSmallThumbs.brands.find((item) => item.id === brand.id);
|
|
assert(listedBrandWithSmallThumbs, 'Brand list with thumbnails should include the created brand');
|
|
assert.equal(listedBrandWithSmallThumbs.pics.length, 1);
|
|
const listedPic = listedBrandWithSmallThumbs.pics[0];
|
|
assert.equal(listedPic.smallThumbW, 40);
|
|
assert.equal(listedPic.smallThumbH, 27);
|
|
assert.equal(typeof listedPic.smallThumbBinary, 'string');
|
|
const listedSmallThumb = Buffer.from(listedPic.smallThumbBinary, 'base64');
|
|
assert.deepEqual(listedSmallThumb, smallThumbRow.smallthumbbinary);
|
|
|
|
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);
|
|
});
|