Merge branch 'codex/feat/traffic-billing-mvp'

# Conflicts:
#	.github/workflows/pipeline.yaml
This commit is contained in:
Haitao Pan 2026-04-11 12:21:51 +08:00
commit 651101c253
4 changed files with 68 additions and 7 deletions

View File

@ -71,7 +71,7 @@ jobs:
push_latest: ${{ steps.push.outputs.push_latest }}
steps:
- name: Check Out Repository
uses: actions/checkout@v5
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Resolve Inputs
id: inputs
@ -118,7 +118,7 @@ jobs:
image_tag: ${{ needs.prep.outputs.image_tag }}
steps:
- name: Check Out Repository
uses: actions/checkout@v5
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set Up Docker Buildx
run: |
@ -152,10 +152,10 @@ jobs:
PLAYBOOKS_REPO: git@github.com:x-evor/playbooks.git
steps:
- name: Check Out Repository
uses: actions/checkout@v5
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set Up Python
uses: actions/setup-python@v6
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
@ -221,7 +221,7 @@ jobs:
if: ${{ always() && needs.deploy.result == 'success' }}
steps:
- name: Check Out Repository
uses: actions/checkout@v5
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Verify Frontend Release
run: |

52
src/lib/apiProxy.test.ts Normal file
View File

@ -0,0 +1,52 @@
// @vitest-environment node
import { afterEach, describe, expect, it, vi } from 'vitest'
import { NextRequest } from 'next/server'
import { proxyRequestToUpstream } from './apiProxy'
describe('proxyRequestToUpstream', () => {
afterEach(() => {
vi.unstubAllGlobals()
})
it('strips stale encoding headers when the upstream body is already decoded', async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify([{ name: 'JP', address: 'jp-xhttp.svc.plus' }]), {
status: 200,
headers: {
'Content-Encoding': 'gzip',
'Content-Length': '405',
'Content-Type': 'application/json; charset=utf-8',
},
}),
)
vi.stubGlobal('fetch', fetchMock)
const response = await proxyRequestToUpstream(
new NextRequest('https://console.svc.plus/api/agent/nodes', {
headers: {
host: 'console.svc.plus',
},
}),
{
upstreamBaseUrl: 'https://accounts.svc.plus',
upstreamPathPrefix: '/api/agent',
},
)
expect(fetchMock).toHaveBeenCalledWith(
'https://accounts.svc.plus/api/agent/nodes',
expect.objectContaining({
cache: 'no-store',
method: 'GET',
redirect: 'manual',
}),
)
expect(response.status).toBe(200)
expect(response.headers.get('content-encoding')).toBeNull()
expect(response.headers.get('content-length')).toBeNull()
expect(response.headers.get('content-type')).toBe('application/json; charset=utf-8')
await expect(response.json()).resolves.toEqual([{ name: 'JP', address: 'jp-xhttp.svc.plus' }])
})
})

View File

@ -14,6 +14,14 @@ const DEFAULT_FORWARD_HEADERS = [
'x-trace-id',
] as const
const RESPONSE_HEADERS_TO_STRIP = new Set([
'connection',
'content-encoding',
'content-length',
'keep-alive',
'transfer-encoding',
])
const BODYLESS_METHODS = new Set(['GET', 'HEAD'])
type ProxyOptions = {
@ -107,7 +115,8 @@ export async function proxyRequestToUpstream(request: NextRequest, options: Prox
const responseHeaders = new Headers()
upstreamResponse.headers.forEach((value, key) => {
if (key.toLowerCase() === 'set-cookie') {
const normalizedKey = key.toLowerCase()
if (normalizedKey === 'set-cookie' || RESPONSE_HEADERS_TO_STRIP.has(normalizedKey)) {
return
}
responseHeaders.set(key, value)

View File

@ -20,7 +20,7 @@ describe('fetchAgentNodes', () => {
await expect(fetchAgentNodes()).resolves.toEqual([{ name: 'JP', address: 'jp-xhttp.svc.plus' }])
expect(fetchMock).toHaveBeenCalledTimes(1)
expect(fetchMock).toHaveBeenCalledWith(
'/api/agent-server/v1/nodes',
'/api/auth/sync/config?since_version=0',
expect.objectContaining({
cache: 'no-store',
credentials: 'include',