From d1c310ab633d83ae5270337c1af7dbe237d42bec Mon Sep 17 00:00:00 2001 From: kl <632104866@QQ.com> Date: Mon, 9 Mar 2026 12:44:48 +0800 Subject: [PATCH] test(e2e): expand archive format coverage (tar/tgz/7z/rar) (#720) * test(e2e): expand archive coverage to tar/tgz/7z/rar * test: address copilot archive fixture review feedback * test(e2e): add rar smoke coverage and align archive deps * test(e2e): make tgz fixture gzip header deterministic * test(e2e): keep legacy zip temp dir ignored * test(e2e): address remaining copilot review comments * test(e2e): make 7z fixture generation deterministic and strict * test(e2e): fail fast when sample.rar fixture is missing --- .github/workflows/nightly-e2e.yml | 4 +- .github/workflows/pr-e2e-mvp.yml | 4 +- tests/e2e/.gitignore | 2 +- tests/e2e/README.md | 4 +- tests/e2e/fixtures/sample.7z | Bin 0 -> 177 bytes tests/e2e/fixtures/sample.rar | Bin 0 -> 336 bytes tests/e2e/fixtures/sample.tar | Bin 0 -> 10240 bytes tests/e2e/fixtures/sample.tgz | Bin 0 -> 154 bytes tests/e2e/scripts/generate-fixtures.mjs | 102 +++++++++++++++++++++--- tests/e2e/specs/preview-smoke.spec.ts | 28 ++++++- 10 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 tests/e2e/fixtures/sample.7z create mode 100644 tests/e2e/fixtures/sample.rar create mode 100644 tests/e2e/fixtures/sample.tar create mode 100644 tests/e2e/fixtures/sample.tgz diff --git a/.github/workflows/nightly-e2e.yml b/.github/workflows/nightly-e2e.yml index 3543706b..431ff4a0 100644 --- a/.github/workflows/nightly-e2e.yml +++ b/.github/workflows/nightly-e2e.yml @@ -36,10 +36,10 @@ jobs: with: python-version: '3.11' - - name: Install LibreOffice + zip + - name: Install LibreOffice + archive tools run: | sudo apt-get update - sudo apt-get install -y libreoffice zip + sudo apt-get install -y libreoffice zip p7zip-full - name: Setup Python deps for office fixtures run: | diff --git a/.github/workflows/pr-e2e-mvp.yml b/.github/workflows/pr-e2e-mvp.yml index 161b525d..62e111d5 100644 --- a/.github/workflows/pr-e2e-mvp.yml +++ b/.github/workflows/pr-e2e-mvp.yml @@ -36,10 +36,10 @@ jobs: with: python-version: '3.11' - - name: Install LibreOffice + zip + - name: Install LibreOffice + archive tools run: | sudo apt-get update - sudo apt-get install -y libreoffice zip + sudo apt-get install -y libreoffice zip p7zip-full - name: Setup Python deps for office fixtures run: | diff --git a/tests/e2e/.gitignore b/tests/e2e/.gitignore index f3b87092..55cbce0a 100644 --- a/tests/e2e/.gitignore +++ b/tests/e2e/.gitignore @@ -3,7 +3,7 @@ playwright-report/ test-results/ __pycache__/ -fixtures/zip-tmp/ +fixtures/archive-tmp/ fixtures/sample.docx fixtures/sample.xlsx fixtures/sample.pptx diff --git a/tests/e2e/README.md b/tests/e2e/README.md index c5336162..2d7bcb29 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -6,7 +6,7 @@ This folder contains a first MVP of end-to-end automated tests. - Basic preview smoke checks for common file types (txt/md/json/xml/csv/html/png) - Office Phase-2 smoke checks (docx/xlsx/pptx) -- Archive smoke check (zip) +- Archive smoke checks (zip/tar/tgz/7z/rar) - Basic endpoint reachability - Security regression checks for blocked internal-network hosts (`10.*`) on: - `/onlinePreview` @@ -31,7 +31,7 @@ npx playwright install --with-deps chromium pip3 install -r requirements.txt ``` -> Prerequisite: ensure `zip` command is available in PATH (used for `sample.zip` fixture generation). +> Prerequisite: ensure `python3`, `zip`, and `7z` (or `bsdtar` as a fallback) are available in PATH for archive fixtures. 3. Generate fixtures and start fixture server: diff --git a/tests/e2e/fixtures/sample.7z b/tests/e2e/fixtures/sample.7z new file mode 100644 index 0000000000000000000000000000000000000000..527017d1c3329b13113960e8bdfc27833de32605 GIT binary patch literal 177 zcmXr7+Ou9=hJm^ElLfOn0|aD2X@MiFo-mj`6uFdRxbA4~64R4)6;J*@-Z-1H@xscr z9}1N&ajdKVB>(?s)dL1b7B&V(PIU%$Zbk-1WoAZ3)>sCH1_mBk1`aO9j}@6a7#LU? z1w|P$8S;QIm7$11kD-L20*DzHM7S6k_E=m}$Xb7gQ3#1G3}mm)z5*5(Wn*MeXjsz7 GzyJV@oHD5Z literal 0 HcmV?d00001 diff --git a/tests/e2e/fixtures/sample.rar b/tests/e2e/fixtures/sample.rar new file mode 100644 index 0000000000000000000000000000000000000000..a0e135fcf1d896265481f60ecef58776775ff300 GIT binary patch literal 336 zcmWGaEK-zWXE;Bhn1O+p0RmcrN)#p-F^B+pAk6Hv=)}J5Ww-1^3^*8;G%_%hq!yRx zl~j~8aDYTX7$l-ll3G!skdmKVnwy$e!pk*RA8ZN-&;$@>u68-O?V`^Ouqpo+LQKiY z%*#H&0TK;}hnNB~_v{0pPAgRVlgmN&3&8D9$t;S2x#PeYkSqvebH~CHB@ZST0$l(B z%peM48au;VM~FLsra=rz0UGk`IZ%TcR5{S$AlBT~SAo{^z%``i7L-&1bv2v;$sCQn NWp~83n!$mc0RW=DZ@B;f literal 0 HcmV?d00001 diff --git a/tests/e2e/fixtures/sample.tar b/tests/e2e/fixtures/sample.tar new file mode 100644 index 0000000000000000000000000000000000000000..6a4c4f6d7ae19b5f5b20c431449d2c9b18b71fd4 GIT binary patch literal 10240 zcmeIwO$x&x5Qbro;t9%FjlDsS(0wSD4k-y(`*Zy2D$qr{$|B^Q&4>fT=Yu+qDeua= znB_`p#Sl#EwZ6NE?~EM8`+ab+&7BL;87s?0nm0f0rO!1|o~EA$|K{_?Rh@?6sJC>| z^s;?EU-g-+wl85X^?Fq=AuIw2AbCPSXKhcZjmCtN9e#cR{_+fl1+qU4IN z&wrG~?7A?u`OxniyC0i470vo~u6ech)c1_ZNi}JoXRTT&d^dyR^^41Qs^uq(FW=N~ zdgr&LYlD`(e9LQh_Wu=woVQcTr{%BH|GhYN^X8wbcNSh{Vn7BzLiugKmWwiIFfafB Dng~bx literal 0 HcmV?d00001 diff --git a/tests/e2e/scripts/generate-fixtures.mjs b/tests/e2e/scripts/generate-fixtures.mjs index afd108cb..ca98657a 100644 --- a/tests/e2e/scripts/generate-fixtures.mjs +++ b/tests/e2e/scripts/generate-fixtures.mjs @@ -16,20 +16,100 @@ write('sample.xml', 'kkFileViewtrue'); write('sample.csv', 'name,value\nkkFileView,1\ne2e,1\n'); write('sample.html', '

kkFileView fixture

'); -// zip (contains txt) - only generate if missing to avoid noisy local diffs -const zipPath = path.join(fixturesDir, 'sample.zip'); -if (!fs.existsSync(zipPath)) { - const zipWork = path.join(fixturesDir, 'zip-tmp'); - fs.mkdirSync(zipWork, { recursive: true }); - fs.writeFileSync(path.join(zipWork, 'inner.txt'), 'kkFileView zip inner file'); +// archive fixtures (contains inner.txt) - generate if missing +const archiveWork = path.join(fixturesDir, 'archive-tmp'); +fs.mkdirSync(archiveWork, { recursive: true }); +const innerFile = path.join(archiveWork, 'inner.txt'); +fs.writeFileSync(innerFile, 'kkFileView archive inner file'); + +const ensureArchive = (name, generator) => { + const out = path.join(fixturesDir, name); + if (fs.existsSync(out)) return; try { - execFileSync('zip', ['-X', '-q', '-r', zipPath, 'inner.txt'], { cwd: zipWork }); + generator(out); } catch (err) { - console.error('Failed to create sample.zip fixture. Ensure "zip" is installed and available in PATH.'); - throw err instanceof Error ? err : new Error(String(err)); - } finally { - fs.rmSync(zipWork, { recursive: true, force: true }); + try { + fs.rmSync(out, { force: true }); + } catch { + // ignore cleanup errors; original error will be rethrown + } + throw err; } +}; + +const buildDeterministicTar = (out, gzip = false) => { + const py = String.raw`import io, tarfile, gzip +from pathlib import Path + +out = Path(r'''${out}''') +inner_path = Path(r'''${innerFile}''') +data = inner_path.read_bytes() +use_gzip = ${gzip ? 'True' : 'False'} + +if use_gzip: + with out.open('wb') as f: + with gzip.GzipFile(filename='', mode='wb', fileobj=f, mtime=0) as gz: + with tarfile.open(fileobj=gz, mode='w', format=tarfile.USTAR_FORMAT) as tf: + info = tarfile.TarInfo('inner.txt') + info.size = len(data) + info.mtime = 946684800 # 2000-01-01 00:00:00 UTC + info.uid = 0 + info.gid = 0 + info.uname = 'root' + info.gname = 'root' + tf.addfile(info, io.BytesIO(data)) +else: + with tarfile.open(out, mode='w', format=tarfile.USTAR_FORMAT) as tf: + info = tarfile.TarInfo('inner.txt') + info.size = len(data) + info.mtime = 946684800 # 2000-01-01 00:00:00 UTC + info.uid = 0 + info.gid = 0 + info.uname = 'root' + info.gname = 'root' + tf.addfile(info, io.BytesIO(data)) +`; + execFileSync('python3', ['-c', py]); +}; + +try { + ensureArchive('sample.zip', out => { + execFileSync('zip', ['-X', '-q', '-r', out, 'inner.txt'], { cwd: archiveWork }); + }); + + ensureArchive('sample.tar', out => { + buildDeterministicTar(out, false); + }); + + ensureArchive('sample.tgz', out => { + buildDeterministicTar(out, true); + }); + + ensureArchive('sample.7z', out => { + try { + execFileSync('7z', ['a', '-bd', '-y', '-mtc=off', '-mta=off', '-mtm=off', out, 'inner.txt'], { + cwd: archiveWork, + }); + } catch (err) { + if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') { + execFileSync('bsdtar', ['-a', '-cf', out, 'inner.txt'], { cwd: archiveWork }); + } else { + throw err; + } + } + }); +} catch (err) { + console.error('Failed to create archive fixtures. Ensure python3, zip, 7z (or bsdtar) are available in PATH.'); + throw err instanceof Error ? err : new Error(String(err)); +} finally { + fs.rmSync(archiveWork, { recursive: true, force: true }); +} + +const rarFixture = path.join(fixturesDir, 'sample.rar'); +if (!fs.existsSync(rarFixture)) { + throw new Error( + 'Missing required fixture tests/e2e/fixtures/sample.rar. Restore it from git (e.g. `git checkout -- tests/e2e/fixtures/sample.rar`) before running e2e.' + ); } // 1x1 png diff --git a/tests/e2e/specs/preview-smoke.spec.ts b/tests/e2e/specs/preview-smoke.spec.ts index afa769b4..03df155d 100644 --- a/tests/e2e/specs/preview-smoke.spec.ts +++ b/tests/e2e/specs/preview-smoke.spec.ts @@ -25,6 +25,10 @@ test.beforeAll(async () => { 'sample.xlsx', 'sample.pptx', 'sample.zip', + 'sample.tar', + 'sample.tgz', + 'sample.7z', + 'sample.rar', ]; try { @@ -97,13 +101,33 @@ test('12 zip preview', async ({ request }) => { expect(resp.status()).toBe(200); }); -test('13 security: block 10.x host in onlinePreview', async ({ request }) => { +test('13 tar preview', async ({ request }) => { + const resp = await openPreview(request, `${fixtureBase}/sample.tar`); + expect(resp.status()).toBe(200); +}); + +test('14 tgz preview', async ({ request }) => { + const resp = await openPreview(request, `${fixtureBase}/sample.tgz`); + expect(resp.status()).toBe(200); +}); + +test('15 7z preview', async ({ request }) => { + const resp = await openPreview(request, `${fixtureBase}/sample.7z`); + expect(resp.status()).toBe(200); +}); + +test('16 rar preview', async ({ request }) => { + const resp = await openPreview(request, `${fixtureBase}/sample.rar`); + expect(resp.status()).toBe(200); +}); + +test('17 security: block 10.x host in onlinePreview', async ({ request }) => { const resp = await openPreview(request, `http://10.1.2.3/a.pdf`); const body = await resp.text(); expect(body).toContain('不受信任'); }); -test('14 security: block 10.x host in getCorsFile', async ({ request }) => { +test('18 security: block 10.x host in getCorsFile', async ({ request }) => { const encoded = b64('http://10.1.2.3/a.pdf'); const resp = await request.get(`/getCorsFile?urlPath=${encoded}`); const body = await resp.text();