generated from coulomb/repo-seed
Introduce npm run smoke with automated build, test, lint, dist, and metadata-only compliance checks. Document manual Chrome steps in RELEASE_SMOKE.md and fix unused imports blocking lint.
103 lines
3.2 KiB
TypeScript
103 lines
3.2 KiB
TypeScript
/**
|
|
* Release smoke — metadata-only compliance checks
|
|
*
|
|
* Verifies the extension never persists PDF content to chrome.storage.
|
|
* Run as part of `npm test` and `npm run smoke`.
|
|
*/
|
|
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
const SRC = path.join(__dirname, '..', 'src');
|
|
|
|
const PDF_CONTENT_FIELD_PATTERN =
|
|
/\b(pdfContent|pdfBytes|contentBase64|base64Content|pdfData)\b/;
|
|
|
|
function readSourceFiles(dir: string): string[] {
|
|
const files: string[] = [];
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const full = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
files.push(...readSourceFiles(full));
|
|
} else if (entry.name.endsWith('.ts')) {
|
|
files.push(full);
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
|
|
describe('metadata-only storage compliance', () => {
|
|
test('DocumentProxy queue does not define PDF content fields', () => {
|
|
const queueSource = fs.readFileSync(
|
|
path.join(SRC, 'utils', 'pdf-queue.ts'),
|
|
'utf-8'
|
|
);
|
|
expect(queueSource).not.toMatch(PDF_CONTENT_FIELD_PATTERN);
|
|
expect(queueSource).toContain('contentHash');
|
|
expect(queueSource).not.toContain('pdfContent');
|
|
});
|
|
|
|
test('DetectedPDF interface is metadata-only', () => {
|
|
const detectorSource = fs.readFileSync(
|
|
path.join(SRC, 'utils', 'pdf-detector.ts'),
|
|
'utf-8'
|
|
);
|
|
const interfaceMatch = detectorSource.match(
|
|
/export interface DetectedPDF \{([^}]+)\}/
|
|
);
|
|
expect(interfaceMatch).not.toBeNull();
|
|
const fields = interfaceMatch![1];
|
|
expect(fields).toMatch(/filename/);
|
|
expect(fields).toMatch(/url/);
|
|
expect(fields).toMatch(/size/);
|
|
expect(fields).not.toMatch(PDF_CONTENT_FIELD_PATTERN);
|
|
});
|
|
|
|
test('no source file persists PDF bytes via chrome.storage', () => {
|
|
const offenders: string[] = [];
|
|
for (const file of readSourceFiles(SRC)) {
|
|
const content = fs.readFileSync(file, 'utf-8');
|
|
if (!content.includes('chrome.storage')) {
|
|
continue;
|
|
}
|
|
const lines = content.split('\n');
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
if (
|
|
line.includes('chrome.storage') &&
|
|
line.includes('.set') &&
|
|
PDF_CONTENT_FIELD_PATTERN.test(line)
|
|
) {
|
|
offenders.push(`${path.relative(SRC, file)}:${i + 1}`);
|
|
}
|
|
}
|
|
}
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
|
|
test('upload path fetches bytes in-memory and delegates to Binect API', () => {
|
|
const popupSource = fs.readFileSync(
|
|
path.join(SRC, 'popup', 'popup.ts'),
|
|
'utf-8'
|
|
);
|
|
expect(popupSource).toMatch(/fetchPDFBytes/);
|
|
expect(popupSource).toMatch(/uploadPDF/);
|
|
expect(popupSource).toMatch(/computeContentFingerprint/);
|
|
});
|
|
|
|
test('manifest declares required MV3 permissions', () => {
|
|
const manifest = JSON.parse(
|
|
fs.readFileSync(
|
|
path.join(__dirname, '..', 'public', 'manifest.json'),
|
|
'utf-8'
|
|
)
|
|
);
|
|
expect(manifest.manifest_version).toBe(3);
|
|
for (const perm of ['downloads', 'storage', 'alarms', 'activeTab']) {
|
|
expect(manifest.permissions).toContain(perm);
|
|
}
|
|
expect(manifest.host_permissions).toEqual(
|
|
expect.arrayContaining(['https://api.binect.de/*'])
|
|
);
|
|
});
|
|
}); |