Skip to content

Commit ca2aaae

Browse files
committed
Add GitHub integration CI coverage
1 parent 0cd47fb commit ca2aaae

28 files changed

+935
-43
lines changed

.github/workflows/ci.yml

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ name: CI
22

33
on:
44
push:
5-
branches: [main, develop]
65
pull_request:
7-
branches: [main, develop]
6+
7+
permissions:
8+
contents: read
9+
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
12+
cancel-in-progress: true
813

914
jobs:
10-
# 后端测试
1115
backend:
1216
name: Backend (Python)
1317
runs-on: ubuntu-latest
18+
timeout-minutes: 15
1419
defaults:
1520
run:
1621
working-directory: apps/api
@@ -36,15 +41,14 @@ jobs:
3641
ruff format --check .
3742
3843
- name: Type check with mypy
39-
run: mypy app --ignore-missing-imports
40-
continue-on-error: true
44+
run: mypy --config-file mypy.ini
4145

4246
- name: Run tests with coverage
43-
run: pytest tests/ -v --cov=app --cov-report=xml --cov-report=term --cov-fail-under=60
47+
run: pytest tests/ -v --cov=app --cov-report=xml --cov-report=term --cov-fail-under=70
4448
env:
45-
DATABASE_URL: "sqlite+aiosqlite:///:memory:"
46-
JWT_SECRET_KEY: "test-secret-key-for-ci"
47-
ENCRYPTION_KEY: "dGVzdC1lbmNyeXB0aW9uLWtleS0zMmJ5dGVz"
49+
DATABASE_URL: sqlite+aiosqlite:///:memory:
50+
JWT_SECRET_KEY: test-secret-key-for-ci
51+
ENCRYPTION_KEY: dGVzdC1lbmNyeXB0aW9uLWtleS0zMmJ5dGVz
4852

4953
- name: Upload coverage to Codecov
5054
uses: codecov/codecov-action@v4
@@ -53,10 +57,10 @@ jobs:
5357
flags: backend
5458
fail_ci_if_error: false
5559

56-
# 前端测试
5760
frontend:
5861
name: Frontend (Next.js)
5962
runs-on: ubuntu-latest
63+
timeout-minutes: 15
6064
defaults:
6165
run:
6266
working-directory: apps/web
@@ -80,19 +84,25 @@ jobs:
8084
- name: Type check
8185
run: npm run type-check
8286

83-
- name: Run tests
84-
run: npm run test --if-present
87+
- name: Run unit tests
88+
run: npm run test
8589

8690
- name: Build
8791
run: npm run build
8892
env:
8993
NEXT_PUBLIC_API_URL: http://localhost:8000
94+
INTERNAL_API_URL: http://localhost:8000
9095

91-
# 所有检查通过
9296
ci-success:
9397
name: CI Success
9498
needs: [backend, frontend]
99+
if: always()
95100
runs-on: ubuntu-latest
96101
steps:
97-
- name: All checks passed
98-
run: echo "✅ All CI checks passed!"
102+
- name: Verify required jobs
103+
run: |
104+
if [ "${{ needs.backend.result }}" != "success" ] || [ "${{ needs.frontend.result }}" != "success" ]; then
105+
echo "At least one CI job failed"
106+
exit 1
107+
fi
108+
echo "✅ All CI checks passed!"

.github/workflows/integration.yml

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
name: Integration
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
12+
cancel-in-progress: true
13+
14+
jobs:
15+
backend-integration:
16+
name: Backend Integration
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 20
19+
services:
20+
postgres:
21+
image: postgres:16-alpine
22+
env:
23+
POSTGRES_USER: querygpt
24+
POSTGRES_PASSWORD: querygpt
25+
POSTGRES_DB: querygpt_test
26+
ports:
27+
- 5432:5432
28+
options: >-
29+
--health-cmd "pg_isready -U querygpt -d querygpt_test"
30+
--health-interval 5s
31+
--health-timeout 5s
32+
--health-retries 20
33+
mysql:
34+
image: mysql:8.0
35+
env:
36+
MYSQL_DATABASE: querygpt_test
37+
MYSQL_USER: querygpt
38+
MYSQL_PASSWORD: querygpt
39+
MYSQL_ROOT_PASSWORD: root
40+
ports:
41+
- 3306:3306
42+
options: >-
43+
--health-cmd="mysqladmin ping -h 127.0.0.1 -uquerygpt -pquerygpt"
44+
--health-interval=5s
45+
--health-timeout=5s
46+
--health-retries=20
47+
48+
steps:
49+
- uses: actions/checkout@v4
50+
51+
- name: Set up Python
52+
uses: actions/setup-python@v5
53+
with:
54+
python-version: "3.11"
55+
cache: "pip"
56+
cache-dependency-path: apps/api/pyproject.toml
57+
58+
- name: Install dependencies
59+
working-directory: apps/api
60+
run: |
61+
python -m pip install --upgrade pip
62+
pip install -e ".[dev]"
63+
64+
- name: Start mock LLM gateway
65+
run: |
66+
python scripts/mock_llm_gateway.py > mock-llm.log 2>&1 &
67+
echo $! > /tmp/mock-llm.pid
68+
python - <<'PY'
69+
import time
70+
import urllib.request
71+
72+
for _ in range(30):
73+
try:
74+
with urllib.request.urlopen("http://127.0.0.1:4010/health", timeout=2):
75+
break
76+
except Exception:
77+
time.sleep(1)
78+
else:
79+
raise SystemExit("Mock LLM gateway failed to become healthy")
80+
PY
81+
82+
- name: Run integration tests
83+
working-directory: apps/api
84+
env:
85+
QUERYGPT_TEST_PG_HOST: 127.0.0.1
86+
QUERYGPT_TEST_PG_PORT: "5432"
87+
QUERYGPT_TEST_PG_USER: querygpt
88+
QUERYGPT_TEST_PG_PASSWORD: querygpt
89+
QUERYGPT_TEST_PG_DATABASE: querygpt_test
90+
QUERYGPT_TEST_MYSQL_HOST: 127.0.0.1
91+
QUERYGPT_TEST_MYSQL_PORT: "3306"
92+
QUERYGPT_TEST_MYSQL_USER: querygpt
93+
QUERYGPT_TEST_MYSQL_PASSWORD: querygpt
94+
QUERYGPT_TEST_MYSQL_DATABASE: querygpt_test
95+
QUERYGPT_TEST_MODEL_BASE_URL: http://127.0.0.1:4010/v1
96+
ENCRYPTION_KEY: dGVzdC1lbmNyeXB0aW9uLWtleS0zMmJ5dGVz
97+
run: pytest tests/test_config_integration.py -v
98+
99+
- name: Upload mock gateway log
100+
if: always()
101+
uses: actions/upload-artifact@v4
102+
with:
103+
name: backend-integration-mock-llm-log
104+
path: mock-llm.log
105+
if-no-files-found: ignore
106+
107+
docker-e2e:
108+
name: Docker E2E
109+
runs-on: ubuntu-latest
110+
timeout-minutes: 25
111+
steps:
112+
- uses: actions/checkout@v4
113+
114+
- name: Set up Node.js
115+
uses: actions/setup-node@v4
116+
with:
117+
node-version: "20"
118+
cache: "npm"
119+
cache-dependency-path: apps/web/package-lock.json
120+
121+
- name: Install frontend dependencies
122+
working-directory: apps/web
123+
run: npm ci
124+
125+
- name: Build and start Docker stack
126+
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d --build
127+
128+
- name: Wait for frontend and backend
129+
run: |
130+
for url in http://127.0.0.1:8000/health http://127.0.0.1:3000; do
131+
for _ in $(seq 1 60); do
132+
if curl -fsS "$url" >/dev/null; then
133+
break
134+
fi
135+
sleep 2
136+
done
137+
curl -fsS "$url" >/dev/null
138+
done
139+
140+
- name: Install Playwright browser
141+
working-directory: apps/web
142+
run: npx playwright install --with-deps chromium
143+
144+
- name: Run Playwright smoke test
145+
working-directory: apps/web
146+
env:
147+
PLAYWRIGHT_BASE_URL: http://127.0.0.1:3000
148+
run: npm run test:e2e
149+
150+
- name: Collect Docker logs
151+
if: always()
152+
run: |
153+
docker compose -f docker-compose.yml -f docker-compose.ci.yml logs --no-color > docker-compose.integration.log || true
154+
155+
- name: Upload Docker and Playwright artifacts
156+
if: always()
157+
uses: actions/upload-artifact@v4
158+
with:
159+
name: docker-e2e-artifacts
160+
path: |
161+
docker-compose.integration.log
162+
apps/web/playwright-report
163+
apps/web/test-results
164+
if-no-files-found: ignore
165+
166+
- name: Shutdown Docker stack
167+
if: always()
168+
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml down -v --remove-orphans
169+
170+
start-sh-smoke:
171+
name: start.sh Smoke (${{ matrix.os }})
172+
runs-on: ${{ matrix.os }}
173+
timeout-minutes: 25
174+
strategy:
175+
fail-fast: false
176+
matrix:
177+
os: [ubuntu-latest, macos-latest]
178+
179+
steps:
180+
- uses: actions/checkout@v4
181+
182+
- name: Set up Python
183+
uses: actions/setup-python@v5
184+
with:
185+
python-version: "3.11"
186+
cache: "pip"
187+
cache-dependency-path: apps/api/pyproject.toml
188+
189+
- name: Set up Node.js
190+
uses: actions/setup-node@v4
191+
with:
192+
node-version: "20"
193+
cache: "npm"
194+
cache-dependency-path: apps/web/package-lock.json
195+
196+
- name: Write CI env files
197+
run: |
198+
cat > apps/api/.env <<'EOF'
199+
DATABASE_URL=sqlite+aiosqlite:///./data/querygpt.db
200+
ENCRYPTION_KEY=dGVzdC1lbmNyeXB0aW9uLWtleS0zMmJ5dGVz
201+
EOF
202+
cat > apps/web/.env.local <<'EOF'
203+
NEXT_PUBLIC_API_URL=http://127.0.0.1:8000
204+
INTERNAL_API_URL=http://127.0.0.1:8000
205+
EOF
206+
207+
- name: Setup workspace
208+
run: QUERYGPT_NO_BROWSER=1 ./start.sh setup
209+
210+
- name: Doctor
211+
run: QUERYGPT_NO_BROWSER=1 ./start.sh doctor
212+
213+
- name: Start backend
214+
run: QUERYGPT_NO_BROWSER=1 ./start.sh backend
215+
216+
- name: Start frontend
217+
run: QUERYGPT_NO_BROWSER=1 ./start.sh frontend
218+
219+
- name: Wait for services
220+
run: |
221+
for url in http://127.0.0.1:8000/health http://127.0.0.1:3000; do
222+
for _ in $(seq 1 60); do
223+
if curl -fsS "$url" >/dev/null; then
224+
break
225+
fi
226+
sleep 2
227+
done
228+
curl -fsS "$url" >/dev/null
229+
done
230+
231+
- name: Status
232+
run: ./start.sh status
233+
234+
- name: Upload host smoke logs
235+
if: always()
236+
uses: actions/upload-artifact@v4
237+
with:
238+
name: start-sh-smoke-${{ matrix.os }}
239+
path: |
240+
logs/backend.log
241+
logs/frontend.log
242+
if-no-files-found: ignore
243+
244+
- name: Stop services
245+
if: always()
246+
run: ./start.sh stop
247+
248+
integration-success:
249+
name: Integration Success
250+
needs: [backend-integration, docker-e2e, start-sh-smoke]
251+
if: always()
252+
runs-on: ubuntu-latest
253+
steps:
254+
- name: Verify integration jobs
255+
run: |
256+
if [ "${{ needs.backend-integration.result }}" != "success" ] || \
257+
[ "${{ needs.docker-e2e.result }}" != "success" ] || \
258+
[ "${{ needs.start-sh-smoke.result }}" != "success" ]; then
259+
echo "At least one integration job failed"
260+
exit 1
261+
fi
262+
echo "✅ All integration checks passed!"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ build/
1212
node_modules/
1313
.next/
1414
out/
15+
playwright-report/
16+
test-results/
1517

1618
# Environment
1719
.env
@@ -37,6 +39,7 @@ data/
3739
# Logs
3840
*.log
3941
logs/
42+
mock-llm.log
4043

4144
# Cache
4245
.cache/

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,29 @@ cd apps/web && npm run type-check && npm test && npm run build
349349
./apps/api/run-tests.sh
350350
```
351351

352+
### GitHub CI 覆盖
353+
354+
当前 GitHub Actions 分成两层:
355+
356+
- 快速层:后端 `ruff + mypy(聊天/配置主链路) + pytest`,前端 `lint + type-check + vitest + build`
357+
- 集成层:Docker 全栈启动、Playwright 烟测、`start.sh` 宿主机烟测、SQLite / PostgreSQL / MySQL 连接测试、模型测试假网关
358+
359+
本地复现常用命令:
360+
361+
```bash
362+
# Docker 全栈
363+
docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d --build
364+
365+
# 后端集成测试(需先准备 PostgreSQL / MySQL / mock gateway 环境变量)
366+
cd apps/api && pytest tests/test_config_integration.py -v
367+
368+
# 后端主链路类型检查
369+
cd apps/api && mypy --config-file mypy.ini
370+
371+
# 前端浏览器烟测(需自行启动应用)
372+
cd apps/web && npm run test:e2e
373+
```
374+
352375
</details>
353376

354377
<details>

0 commit comments

Comments
 (0)