generated from coulomb/repo-seed
Add release smoke path for pre-store verification (WP-0002)
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.
This commit is contained in:
103
tests/metadata-only.test.ts
Normal file
103
tests/metadata-only.test.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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/*'])
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user