mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-03-13 20:53:47 +08:00
test(e2e): phase-2 add Office and zip smoke automation (#714)
* test(e2e): phase-2 add office and zip smoke coverage * test(e2e): address copilot review for fixture stability and CI python setup * test(e2e): fix preflight fixture scope and path handling * test(e2e): harden fixture preflight and remove duplicate generation * test(e2e): remove redundant zip install and cleanup temp zip dir * test(e2e): ensure zip dependency and unify python command in docs * docs(e2e): align README with npm gen scripts and python3 usage
This commit is contained in:
17
.github/workflows/pr-e2e-mvp.yml
vendored
17
.github/workflows/pr-e2e-mvp.yml
vendored
@@ -31,10 +31,20 @@ jobs:
|
|||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: tests/e2e/package-lock.json
|
cache-dependency-path: tests/e2e/package-lock.json
|
||||||
|
|
||||||
- name: Install LibreOffice
|
- name: Setup Python 3.11
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install LibreOffice + zip
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libreoffice
|
sudo apt-get install -y libreoffice zip
|
||||||
|
|
||||||
|
- name: Setup Python deps for office fixtures
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r tests/e2e/requirements.txt
|
||||||
|
|
||||||
- name: Build kkFileView
|
- name: Build kkFileView
|
||||||
run: mvn -q -pl server -DskipTests package
|
run: mvn -q -pl server -DskipTests package
|
||||||
@@ -45,9 +55,6 @@ jobs:
|
|||||||
npm install
|
npm install
|
||||||
npx playwright install --with-deps chromium
|
npx playwright install --with-deps chromium
|
||||||
|
|
||||||
- name: Generate fixtures
|
|
||||||
run: node tests/e2e/scripts/generate-fixtures.mjs
|
|
||||||
|
|
||||||
- name: Start fixture server
|
- name: Start fixture server
|
||||||
run: |
|
run: |
|
||||||
cd tests/e2e/fixtures
|
cd tests/e2e/fixtures
|
||||||
|
|||||||
6
tests/e2e/.gitignore
vendored
6
tests/e2e/.gitignore
vendored
@@ -1,3 +1,9 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
playwright-report/
|
playwright-report/
|
||||||
test-results/
|
test-results/
|
||||||
|
|
||||||
|
__pycache__/
|
||||||
|
fixtures/zip-tmp/
|
||||||
|
fixtures/sample.docx
|
||||||
|
fixtures/sample.xlsx
|
||||||
|
fixtures/sample.pptx
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ This folder contains a first MVP of end-to-end automated tests.
|
|||||||
## What is covered
|
## What is covered
|
||||||
|
|
||||||
- Basic preview smoke checks for common file types (txt/md/json/xml/csv/html/png)
|
- 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)
|
||||||
- Basic endpoint reachability
|
- Basic endpoint reachability
|
||||||
- Security regression checks for blocked internal-network hosts (`10.*`) on:
|
- Security regression checks for blocked internal-network hosts (`10.*`) on:
|
||||||
- `/onlinePreview`
|
- `/onlinePreview`
|
||||||
@@ -24,13 +26,16 @@ mvn -q -pl server -DskipTests package
|
|||||||
cd tests/e2e
|
cd tests/e2e
|
||||||
npm install
|
npm install
|
||||||
npx playwright install --with-deps chromium
|
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).
|
||||||
|
|
||||||
3. Generate fixtures and start fixture server:
|
3. Generate fixtures and start fixture server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /path/to/kkFileView
|
cd /path/to/kkFileView
|
||||||
node tests/e2e/scripts/generate-fixtures.mjs
|
npm run gen:all
|
||||||
cd tests/e2e/fixtures && python3 -m http.server 18080
|
cd tests/e2e/fixtures && python3 -m http.server 18080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
BIN
tests/e2e/fixtures/sample.zip
Normal file
BIN
tests/e2e/fixtures/sample.zip
Normal file
Binary file not shown.
@@ -4,9 +4,12 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"gen:fixtures": "node ./scripts/generate-fixtures.mjs",
|
||||||
|
"gen:office": "python3 ./scripts/generate-office-fixtures.py",
|
||||||
|
"gen:all": "npm run gen:fixtures && npm run gen:office",
|
||||||
|
"pretest": "npm run gen:all",
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
"test:headed": "playwright test --headed",
|
"test:headed": "playwright test --headed"
|
||||||
"gen:fixtures": "node ./scripts/generate-fixtures.mjs"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.55.0"
|
"@playwright/test": "^1.55.0"
|
||||||
|
|||||||
3
tests/e2e/requirements.txt
Normal file
3
tests/e2e/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
python-docx==1.1.2
|
||||||
|
openpyxl==3.1.5
|
||||||
|
python-pptx==1.0.2
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
const fixturesDir = path.resolve(process.cwd(), 'tests/e2e/fixtures');
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const fixturesDir = path.resolve(__dirname, '..', 'fixtures');
|
||||||
fs.mkdirSync(fixturesDir, { recursive: true });
|
fs.mkdirSync(fixturesDir, { recursive: true });
|
||||||
|
|
||||||
const write = (name, content) => fs.writeFileSync(path.join(fixturesDir, name), content);
|
const write = (name, content) => fs.writeFileSync(path.join(fixturesDir, name), content);
|
||||||
@@ -13,6 +16,22 @@ write('sample.xml', '<root><name>kkFileView</name><e2e>true</e2e></root>');
|
|||||||
write('sample.csv', 'name,value\nkkFileView,1\ne2e,1\n');
|
write('sample.csv', 'name,value\nkkFileView,1\ne2e,1\n');
|
||||||
write('sample.html', '<!doctype html><html><body><h1>kkFileView fixture</h1></body></html>');
|
write('sample.html', '<!doctype html><html><body><h1>kkFileView fixture</h1></body></html>');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
try {
|
||||||
|
execFileSync('zip', ['-X', '-q', '-r', zipPath, 'inner.txt'], { cwd: zipWork });
|
||||||
|
} 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1x1 png
|
// 1x1 png
|
||||||
write(
|
write(
|
||||||
'sample.png',
|
'sample.png',
|
||||||
|
|||||||
35
tests/e2e/scripts/generate-office-fixtures.py
Normal file
35
tests/e2e/scripts/generate-office-fixtures.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from docx import Document
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from pptx import Presentation
|
||||||
|
|
||||||
|
fixtures = Path(__file__).resolve().parent.parent / "fixtures"
|
||||||
|
fixtures.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# DOCX
|
||||||
|
_doc = Document()
|
||||||
|
_doc.add_heading("kkFileView E2E", level=1)
|
||||||
|
_doc.add_paragraph("This is a DOCX fixture for Phase-2 E2E.")
|
||||||
|
_doc.save(fixtures / "sample.docx")
|
||||||
|
|
||||||
|
# XLSX
|
||||||
|
_wb = Workbook()
|
||||||
|
_ws = _wb.active
|
||||||
|
_ws.title = "Sheet1"
|
||||||
|
_ws["A1"] = "name"
|
||||||
|
_ws["B1"] = "value"
|
||||||
|
_ws["A2"] = "kkFileView"
|
||||||
|
_ws["B2"] = 2
|
||||||
|
_wb.save(fixtures / "sample.xlsx")
|
||||||
|
|
||||||
|
# PPTX
|
||||||
|
_prs = Presentation()
|
||||||
|
slide_layout = _prs.slide_layouts[1]
|
||||||
|
slide = _prs.slides.add_slide(slide_layout)
|
||||||
|
slide.shapes.title.text = "kkFileView E2E"
|
||||||
|
slide.placeholders[1].text = "This is a PPTX fixture for Phase-2 E2E."
|
||||||
|
_prs.save(fixtures / "sample.pptx")
|
||||||
|
|
||||||
|
print("office fixtures generated in", fixtures)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
import { test, expect, request as playwrightRequest } from '@playwright/test';
|
||||||
|
|
||||||
const fixtureBase = process.env.FIXTURE_BASE_URL || 'http://127.0.0.1:18080';
|
const fixtureBase = process.env.FIXTURE_BASE_URL || 'http://127.0.0.1:18080';
|
||||||
|
|
||||||
@@ -11,6 +11,32 @@ async function openPreview(request: any, fileUrl: string) {
|
|||||||
return request.get(`/onlinePreview?url=${encoded}`);
|
return request.get(`/onlinePreview?url=${encoded}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
const api = await playwrightRequest.newContext();
|
||||||
|
const required = [
|
||||||
|
'sample.txt',
|
||||||
|
'sample.md',
|
||||||
|
'sample.json',
|
||||||
|
'sample.xml',
|
||||||
|
'sample.csv',
|
||||||
|
'sample.html',
|
||||||
|
'sample.png',
|
||||||
|
'sample.docx',
|
||||||
|
'sample.xlsx',
|
||||||
|
'sample.pptx',
|
||||||
|
'sample.zip',
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const name of required) {
|
||||||
|
const resp = await api.get(`${fixtureBase}/${name}`);
|
||||||
|
expect(resp.ok(), `fixture missing or unavailable: ${name}`).toBeTruthy();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await api.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('01 home/index reachable', async ({ request }) => {
|
test('01 home/index reachable', async ({ request }) => {
|
||||||
const resp = await request.get('/');
|
const resp = await request.get('/');
|
||||||
expect(resp.status()).toBeLessThan(500);
|
expect(resp.status()).toBeLessThan(500);
|
||||||
@@ -51,13 +77,33 @@ test('08 png preview', async ({ request }) => {
|
|||||||
expect(resp.status()).toBe(200);
|
expect(resp.status()).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('09 security: block 10.x host in onlinePreview', async ({ request }) => {
|
test('09 docx preview', async ({ request }) => {
|
||||||
|
const resp = await openPreview(request, `${fixtureBase}/sample.docx`);
|
||||||
|
expect(resp.status()).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('10 xlsx preview', async ({ request }) => {
|
||||||
|
const resp = await openPreview(request, `${fixtureBase}/sample.xlsx`);
|
||||||
|
expect(resp.status()).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('11 pptx preview', async ({ request }) => {
|
||||||
|
const resp = await openPreview(request, `${fixtureBase}/sample.pptx`);
|
||||||
|
expect(resp.status()).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('12 zip preview', async ({ request }) => {
|
||||||
|
const resp = await openPreview(request, `${fixtureBase}/sample.zip`);
|
||||||
|
expect(resp.status()).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('13 security: block 10.x host in onlinePreview', async ({ request }) => {
|
||||||
const resp = await openPreview(request, `http://10.1.2.3/a.pdf`);
|
const resp = await openPreview(request, `http://10.1.2.3/a.pdf`);
|
||||||
const body = await resp.text();
|
const body = await resp.text();
|
||||||
expect(body).toContain('不受信任');
|
expect(body).toContain('不受信任');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('10 security: block 10.x host in getCorsFile', async ({ request }) => {
|
test('14 security: block 10.x host in getCorsFile', async ({ request }) => {
|
||||||
const encoded = b64('http://10.1.2.3/a.pdf');
|
const encoded = b64('http://10.1.2.3/a.pdf');
|
||||||
const resp = await request.get(`/getCorsFile?urlPath=${encoded}`);
|
const resp = await request.get(`/getCorsFile?urlPath=${encoded}`);
|
||||||
const body = await resp.text();
|
const body = await resp.text();
|
||||||
|
|||||||
Reference in New Issue
Block a user