-
Notifications
You must be signed in to change notification settings - Fork 248
Test/e2e workflow #739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Test/e2e workflow #739
Changes from all commits
1c5c868
aa2266f
caf42cb
0205c8f
3448385
6642ba0
b3d7473
8b51987
3c8a552
69d1320
f91c0e5
a26aacc
5cdf0e9
fb58ef5
3232248
c91eb28
547148a
35f9705
9768739
103b773
530d59e
3f6f7b3
bea43cf
b3031ef
bb1afa2
610947d
205626b
ac8b9cb
8e00070
c87c814
69e7314
8810d03
ce39373
70db454
9d046d5
0b2cab6
6a04255
b20c28e
7c0aa98
b5bafb2
a27ec03
3ccd1cb
b8dbf25
296b1dd
8d738b3
ce00c55
9de3d06
ce69fe7
56514bf
ccb1b2f
1816fde
28ed579
6432e2f
22eb023
0899fff
c266eb5
70d86c1
ed3b88b
08a7c6c
0d9e06f
bedf0ed
d457952
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,284 @@ | ||
| name: Test | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| push: | ||
| branches: [main] | ||
|
|
||
| jobs: | ||
| unit-test: | ||
| name: Unit Tests | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 24 | ||
|
|
||
| - name: Install dependencies | ||
| run: | | ||
| corepack enable | ||
| yarn install --no-immutable | ||
| cd src/webui/FE && npm install | ||
|
|
||
| - name: Run unit tests | ||
| run: npm run test:unit | ||
|
|
||
| - name: Run WebUI tests | ||
| run: npm run test:webui | ||
|
|
||
| e2e-test: | ||
| name: E2E Tests | ||
| runs-on: [self-hosted, llbot-test] | ||
| needs: [unit-test] | ||
| if: github.event_name == 'pull_request' | ||
| steps: | ||
| - name: Cleanup root-owned files | ||
| run: sudo rm -rf ${{ github.workspace }}/workspace1 ${{ github.workspace }}/workspace2 ${{ github.workspace }}/test/onebot11-api-test/node_modules | ||
|
|
||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 24 | ||
|
|
||
| - name: Install dependencies | ||
| run: | | ||
| corepack enable | ||
| yarn install --no-immutable | ||
| cd src/webui/FE && npm install | ||
|
|
||
| - name: Build | ||
| run: | | ||
| npm run build | ||
| npm run build-webui | ||
|
|
||
| - name: Build PR LLBot image | ||
| run: docker build -f docker/Dockerfile.test -t llbot-pr-test . | ||
|
|
||
| - name: Write configs | ||
| run: | | ||
| mkdir -p workspace1/data workspace2/data | ||
|
|
||
| cat > workspace1/data/config_1577491075.json << 'CONF' | ||
| { | ||
| "ob11": { | ||
| "enable": true, | ||
| "connect": [ | ||
| { "type": "http", "enable": true, "host": "", "port": 53000, "token": "123", "messageFormat": "array", "reportSelfMessage": false, "reportOfflineMessage": false, "debug": false } | ||
| ] | ||
| }, | ||
| "satori": { "enable": false }, | ||
| "milky": { "enable": false }, | ||
| "webui": { "enable": false } | ||
| } | ||
| CONF | ||
|
|
||
| cat > workspace2/data/config_412805684.json << 'CONF' | ||
| { | ||
| "ob11": { | ||
| "enable": true, | ||
| "connect": [ | ||
| { "type": "http", "enable": true, "host": "", "port": 53001, "token": "123", "messageFormat": "array", "reportSelfMessage": false, "reportOfflineMessage": false, "debug": false } | ||
| ] | ||
| }, | ||
| "satori": { "enable": false }, | ||
| "milky": { "enable": false }, | ||
| "webui": { "enable": false } | ||
| } | ||
| CONF | ||
|
|
||
| - name: Restart PMHQ with workspace mount | ||
| run: | | ||
| docker stop pmhq1 pmhq2 | ||
|
|
||
| docker rm -f pmhq1-test pmhq2-test 2>/dev/null || true | ||
|
|
||
| docker run -d --name pmhq1-test \ | ||
| --network llbot-test_test_network \ | ||
| --privileged \ | ||
| -e ENABLE_HEADLESS=false \ | ||
| -e AUTO_LOGIN_QQ=1577491075 \ | ||
| -v llbot-test_qq_volume1:/root/.config/QQ \ | ||
| -v ${{ github.workspace }}:/app/workspace:rw \ | ||
| -v ${{ github.workspace }}/workspace1/data:/app/llbot/data:rw \ | ||
| linyuchen/pmhq:latest | ||
|
|
||
| docker run -d --name pmhq2-test \ | ||
| --network llbot-test_test_network \ | ||
| --privileged \ | ||
| -e ENABLE_HEADLESS=false \ | ||
| -e AUTO_LOGIN_QQ=412805684 \ | ||
| -v llbot-test_qq_volume2:/root/.config/QQ \ | ||
| -v ${{ github.workspace }}:/app/workspace:rw \ | ||
| -v ${{ github.workspace }}/workspace2/data:/app/llbot/data:rw \ | ||
| linyuchen/pmhq:latest | ||
|
|
||
| echo "Waiting for PMHQ to be ready..." | ||
| for i in $(seq 1 30); do | ||
| H1=$(docker exec pmhq1-test curl -sf http://localhost:13000/health 2>/dev/null || true) | ||
| H2=$(docker exec pmhq2-test curl -sf http://localhost:13000/health 2>/dev/null || true) | ||
| [ -n "$H1" ] && [ -n "$H2" ] && echo "Both PMHQ ready" && break | ||
| echo "Waiting for PMHQ... ($i/30)" | ||
| sleep 5 | ||
| done | ||
|
|
||
| - name: Start PR LLBot containers | ||
| run: | | ||
| docker rm -f llbot-pr1 llbot-pr2 2>/dev/null || true | ||
|
|
||
| docker run -d --name llbot-pr1 \ | ||
| --network llbot-test_test_network \ | ||
| -e pmhq_host=pmhq1-test \ | ||
| -e pmhq_port=13000 \ | ||
| -e WEBUI_PORT=3091 \ | ||
| -v llbot-test_qq_volume1:/root/.config/QQ \ | ||
| -v ${{ github.workspace }}/workspace1/data:/app/llbot/data:rw \ | ||
| -v ${{ github.workspace }}:/app/workspace:ro \ | ||
| llbot-pr-test | ||
|
|
||
| docker run -d --name llbot-pr2 \ | ||
| --network llbot-test_test_network \ | ||
| -e pmhq_host=pmhq2-test \ | ||
| -e pmhq_port=13000 \ | ||
| -e WEBUI_PORT=3092 \ | ||
| -v llbot-test_qq_volume2:/root/.config/QQ \ | ||
| -v ${{ github.workspace }}/workspace2/data:/app/llbot/data:rw \ | ||
| -v ${{ github.workspace }}:/app/workspace:ro \ | ||
| llbot-pr-test | ||
|
|
||
| - name: Wait for LLBot ready | ||
| run: | | ||
| for i in $(seq 1 60); do | ||
| R1=$(docker exec llbot-pr1 curl -sf http://127.0.0.1:53000/get_login_info -H "Authorization: Bearer 123" 2>/dev/null || true) | ||
| R2=$(docker exec llbot-pr2 curl -sf http://127.0.0.1:53001/get_login_info -H "Authorization: Bearer 123" 2>/dev/null || true) | ||
| [ -n "$R1" ] && [ -n "$R2" ] && echo "Both LLBot instances ready" && break | ||
| echo "Waiting... ($i/60)" | ||
| sleep 5 | ||
| done | ||
|
|
||
| - name: Write test config | ||
| run: | | ||
| cat > ${{ github.workspace }}/test/onebot11-api-test/config/test.config.json << 'CONF' | ||
| { | ||
| "accounts": { | ||
| "primary": { | ||
| "host": "http://llbot-pr1:53000", | ||
| "apiKey": "123", | ||
| "protocol": "http", | ||
| "user_id": "1577491075" | ||
| }, | ||
| "secondary": { | ||
| "host": "http://llbot-pr2:53001", | ||
| "apiKey": "123", | ||
| "protocol": "http", | ||
| "user_id": "412805684" | ||
| } | ||
| }, | ||
| "test_group_id": "1098066573", | ||
| "timeout": 30000, | ||
| "retryAttempts": 3 | ||
| } | ||
| CONF | ||
|
|
||
| - name: Run e2e tests in Docker | ||
| run: | | ||
| docker run --rm --name e2e-runner \ | ||
| --network llbot-test_test_network \ | ||
| -v ${{ github.workspace }}:/app/workspace:rw \ | ||
| -w /app/workspace/test/onebot11-api-test \ | ||
| node:24-alpine3.23 sh -c "npm install && npm test" | ||
| continue-on-error: true | ||
|
|
||
| - name: Show test summary | ||
| if: always() | ||
| run: | | ||
| if [ -f test/onebot11-api-test/test-report.html ]; then | ||
| echo "Test report generated" | ||
| else | ||
| echo "No test report found" | ||
| fi | ||
|
|
||
| - name: Stop test containers and restore PMHQ | ||
| if: always() | ||
| run: | | ||
| echo "=== LLBot1 logs ===" && docker logs llbot-pr1 2>&1 | tail -30 || true | ||
| echo "=== LLBot2 logs ===" && docker logs llbot-pr2 2>&1 | tail -30 || true | ||
| docker rm -f llbot-pr1 llbot-pr2 pmhq1-test pmhq2-test 2>/dev/null || true | ||
| docker start pmhq1 pmhq2 2>/dev/null || true | ||
| sudo rm -rf ${{ github.workspace }}/workspace1 ${{ github.workspace }}/workspace2 | ||
|
|
||
| - name: Upload test report | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: e2e-test-report | ||
| path: test/onebot11-api-test/test-report.html | ||
| retention-days: 30 | ||
|
|
||
| report: | ||
| name: Test Report | ||
| runs-on: ubuntu-latest | ||
| needs: [unit-test, e2e-test] | ||
| if: always() && github.event_name == 'pull_request' | ||
| permissions: | ||
| pull-requests: write | ||
| steps: | ||
| - name: Generate report comment | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const jobs = { | ||
| 'unit-test': '${{ needs.unit-test.result }}', | ||
| 'e2e-test': '${{ needs.e2e-test.result }}', | ||
| }; | ||
|
|
||
| const icons = { success: '✅', failure: '❌', skipped: '⏭️', cancelled: '🚫' }; | ||
| const lines = Object.entries(jobs).map(([name, result]) => | ||
| `| ${name} | ${icons[result] || '❓'} ${result} |` | ||
| ); | ||
|
|
||
| const allPassed = Object.values(jobs).every(r => r === 'success' || r === 'skipped'); | ||
| const summary = allPassed ? '✅ All tests passed' : '❌ Some tests failed'; | ||
|
|
||
| const body = [ | ||
| `## Test Report`, | ||
| ``, | ||
| `| Job | Status |`, | ||
| `|-----|--------|`, | ||
| ...lines, | ||
| ``, | ||
| summary, | ||
| ].join('\n'); | ||
|
|
||
| // Find existing comment | ||
| const { data: comments } = await github.rest.issues.listComments({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| }); | ||
| const botComment = comments.find(c => | ||
| c.user.type === 'Bot' && c.body.includes('## Test Report') | ||
| ); | ||
|
|
||
| if (botComment) { | ||
| await github.rest.issues.updateComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| comment_id: botComment.id, | ||
| body, | ||
| }); | ||
| } else { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,6 @@ | |
| !.yarn/versions | ||
|
|
||
| package-lock.json | ||
| yarn.lock | ||
| node_modules | ||
| dist | ||
| out | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,6 @@ | ||
| approvedGitRepositories: | ||
| - "**" | ||
|
|
||
| enableScripts: true | ||
|
|
||
| nodeLinker: node-modules | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| FROM node:24-alpine3.23 | ||
| RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \ | ||
| apk add --no-cache ffmpeg curl | ||
| WORKDIR /app/llbot | ||
| COPY dist/ . | ||
| COPY docker/startup.sh /startup.sh | ||
| RUN chmod +x /startup.sh | ||
| ENTRYPOINT ["/startup.sh"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| {"name":"llonebot-dist","version":"7.12.2","type":"module","description":"","main":"llbot.js","author":"linyuchen","repository":{"type":"git","url":"https://github.com/LLOneBot/LuckyLilliaBot"}} | ||
| {"name":"llonebot-dist","version":"7.12.2","type":"module","description":"","main":"llbot.js","author":"linyuchen","repository":{"type":"git","url":"https://github.com/LLOneBot/LuckyLilliaBot"}} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,7 +99,7 @@ async function onLoad() { | |
| } | ||
|
|
||
| const isDocker = isDockerEnvironment() | ||
| let qrCodeTriggered = false | ||
| let lastQrCodeTime = 0 | ||
|
|
||
| const printLoginQrCode = async () => { | ||
| try { | ||
|
|
@@ -135,8 +135,9 @@ async function onLoad() { | |
| return | ||
| } | ||
| if (!pmhqSelfInfo.online) { | ||
| if (isDocker && !qrCodeTriggered) { | ||
| qrCodeTriggered = true | ||
| const now = Date.now() | ||
| if (isDocker && now - lastQrCodeTime > 120_000) { | ||
| lastQrCodeTime = now | ||
| printLoginQrCode() | ||
| } | ||
| setTimeout(checkLogin, 1000) | ||
|
|
@@ -172,6 +173,7 @@ async function onLoad() { | |
| ctx.inject(['logger'], (ctx) => { | ||
| ctx.logger.exporter(new Log(ctx, true)) | ||
| ctx.logger.info(`LLBot ${version}`) | ||
| ctx.logger.info(process.argv) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 issue (security): 在启动时记录 CLI 参数中往往包含 token、密码或其他机密信息。每次启动都记录这些参数,会有将凭据泄露到日志或外部日志聚合系统的风险。如果你只是为了排查问题需要这条日志,请将其放在调试开关之后,并/或对敏感值做脱敏处理,或者干脆删除这条日志。 Original comment in English🚨 issue (security): Logging CLI arguments often include tokens, passwords, or other secrets. Logging them on every start risks leaking credentials to logs and external aggregators. If you need this for troubleshooting, please gate it behind a debug flag and/or redact sensitive values, or remove it entirely. |
||
| }) | ||
| // setFFMpegPath(config.ffmpeg || '') | ||
| ctx.inject(['pmhq', 'config', 'logger'], (ctx) => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚨 issue (security): 全局启用脚本并允许所有 git 仓库会显著增加供应链风险。
使用 `approvedGitRepositories: [
Original comment in English
🚨 issue (security): Enabling scripts globally and approving all git repositories can increase supply-chain risk.
With `approvedGitRepositories: [