mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-19 12:43:39 +00:00
ci: gate merges on deterministic no-AK E2E (#8313)
* test(sdk-typescript): stabilize permission mode round trips * test(sdk-typescript): make permission control deterministic * test(sdk-typescript): make orchestration E2E deterministic * test(sdk-typescript): make orchestration E2E deterministic * ci: gate merges on deterministic no-AK E2E * ci: gate merges on deterministic no-AK E2E * Merge PR #8302 review follow-up * test(sdk-typescript): distinguish plan-mode enforcement * test(sdk-typescript): distinguish plan-mode enforcement * ci: validate stacked gate against main * ci: validate stacked gate against main * test(sdk-typescript): reuse fake tool server * test(sdk-typescript): reuse fake tool server * test(sdk-typescript): stabilize model switch timing * test(sdk-typescript): ignore E2E side queries * fix(ci): enforce no-AK gate invariants * test(ci): cover no-AK gate event wiring * fix(ci): isolate no-AK gate environment * test(ci): anchor credential isolation checks * fix(ci): complete no-AK gate credential clearing and restore closed-query tests (#8313) * fix(ci): harden no-AK gate per review feedback (#8313) * test(ci): assert timeout-minutes on no-AK gate step (#8313) --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
This commit is contained in:
parent
8d6d2ab56a
commit
7100ffb816
6 changed files with 349 additions and 458 deletions
38
.github/workflows/ci.yml
vendored
38
.github/workflows/ci.yml
vendored
|
|
@ -408,9 +408,41 @@ jobs:
|
|||
node -e "const fs = require('node:fs'); for (const key of ['HOME', 'USERPROFILE']) { const dir = process.env[key]; if (dir) fs.mkdirSync(dir, { recursive: true }); }"
|
||||
npm run test:ci
|
||||
|
||||
- name: 'Run no-AK integration smoke tests'
|
||||
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' && github.event_name == 'pull_request' }}"
|
||||
run: 'npm run test:integration:no-ak:sandbox:none'
|
||||
- name: 'Run required no-AK integration gate'
|
||||
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' && (github.event_name == 'pull_request' || github.event_name == 'merge_group') }}"
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
HOME: '${{ runner.temp }}/qwen-no-ak-home'
|
||||
USERPROFILE: '${{ runner.temp }}/qwen-no-ak-home'
|
||||
QWEN_HOME: '${{ runner.temp }}/qwen-no-ak-home/.qwen'
|
||||
API_KEY: ''
|
||||
ANTHROPIC_API_KEY: ''
|
||||
ANTHROPIC_BASE_URL: ''
|
||||
ANTHROPIC_MODEL: ''
|
||||
BAILIAN_CODING_PLAN_API_KEY: ''
|
||||
BAILIAN_TOKEN_PLAN_API_KEY: ''
|
||||
DEEPSEEK_API_KEY: ''
|
||||
OPENAI_API_KEY: ''
|
||||
OPENAI_BASE_URL: ''
|
||||
OPENAI_MODEL: ''
|
||||
DASHSCOPE_API_KEY: ''
|
||||
GOOGLE_API_KEY: ''
|
||||
GOOGLE_MODEL: ''
|
||||
QWEN_API_KEY: ''
|
||||
QWEN_MODEL: ''
|
||||
GEMINI_API_KEY: ''
|
||||
GEMINI_MODEL: ''
|
||||
IDEALAB_API_KEY: ''
|
||||
MINIMAX_API_KEY: ''
|
||||
MODELSCOPE_API_KEY: ''
|
||||
OPENROUTER_API_KEY: ''
|
||||
REQUESTY_API_KEY: ''
|
||||
XAI_API_KEY: ''
|
||||
ZAI_API_KEY: ''
|
||||
QWEN_DEFAULT_AUTH_TYPE: ''
|
||||
run: |-
|
||||
mkdir -p "${HOME}" "${QWEN_HOME}"
|
||||
npm run test:integration:no-ak:sandbox:none
|
||||
|
||||
- name: 'Publish Test Report (for non-forks)'
|
||||
if: |-
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -203,6 +203,31 @@ describe('System Control (E2E)', () => {
|
|||
await fakeServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw error when setModel is called on closed query', async () => {
|
||||
const fakeServer = await startFakeOpenAIServer(
|
||||
() => ({ content: 'Done.' }),
|
||||
FAKE_SERVER_OPTIONS,
|
||||
);
|
||||
try {
|
||||
const q = query({
|
||||
prompt: 'Hello',
|
||||
options: {
|
||||
...SHARED_TEST_OPTIONS,
|
||||
...fakeModelOptions(fakeServer.baseUrl),
|
||||
cwd: testDir,
|
||||
},
|
||||
});
|
||||
|
||||
await q.close();
|
||||
|
||||
await expect(q.setModel('fake-model-2')).rejects.toThrow(
|
||||
'Query is closed',
|
||||
);
|
||||
} finally {
|
||||
await fakeServer.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('supportedCommands API', () => {
|
||||
|
|
@ -284,5 +309,28 @@ describe('System Control (E2E)', () => {
|
|||
await fakeServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw error when supportedCommands is called on closed query', async () => {
|
||||
const fakeServer = await startFakeOpenAIServer(
|
||||
() => ({ content: 'Done.' }),
|
||||
FAKE_SERVER_OPTIONS,
|
||||
);
|
||||
try {
|
||||
const q = query({
|
||||
prompt: 'Hello',
|
||||
options: {
|
||||
...SHARED_TEST_OPTIONS,
|
||||
...fakeModelOptions(fakeServer.baseUrl),
|
||||
cwd: testDir,
|
||||
},
|
||||
});
|
||||
|
||||
await q.close();
|
||||
|
||||
await expect(q.supportedCommands()).rejects.toThrow('Query is closed');
|
||||
} finally {
|
||||
await fakeServer.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
"test:integration:sandbox:none": "cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests",
|
||||
"test:integration:sandbox:docker": "cross-env QWEN_SANDBOX=docker npm run build:sandbox && QWEN_SANDBOX=docker vitest run --root ./integration-tests",
|
||||
"test:integration:sandbox:podman": "cross-env QWEN_SANDBOX=podman vitest run --root ./integration-tests",
|
||||
"test:integration:no-ak:sandbox:none": "cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests ./fake-openai-server.test.ts ./cli/qwen-serve-routes.test.ts ./cli/qwen-serve-streaming.test.ts ./cli/daemon-invocation-context.test.ts",
|
||||
"test:integration:no-ak:sandbox:none": "cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests --maxWorkers 2 ./fake-openai-server.test.ts ./cli/daemon-invocation-context.test.ts ./cli/list_directory.test.ts ./cli/qwen-serve-routes.test.ts ./cli/qwen-serve-streaming.test.ts ./sdk-typescript/abort-and-lifecycle.test.ts ./sdk-typescript/permission-control.test.ts ./sdk-typescript/sdk-mcp-server.test.ts ./sdk-typescript/subagents.test.ts ./sdk-typescript/system-control.test.ts ./sdk-typescript/tool-control.test.ts",
|
||||
"test:integration:sdk:sandbox:none": "cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests --poolOptions.threads.maxThreads 2 sdk-typescript",
|
||||
"test:integration:sdk:sandbox:docker": "cross-env QWEN_SANDBOX=docker npm run build:sandbox && QWEN_SANDBOX=docker vitest run --root ./integration-tests --poolOptions.threads.maxThreads 2 sdk-typescript",
|
||||
"test:sdk:python": "python3 -m pytest -c packages/sdk-python/pyproject.toml packages/sdk-python/tests -q",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ export {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** All known providers, in display order. */
|
||||
// Adding a provider that reads credentials from a new env key? Also add that
|
||||
// key to the no-AK gate's cleared-env list in .github/workflows/ci.yml and to
|
||||
// scripts/tests/no-ak-integration-ci.test.js, or the gate can leak runner creds.
|
||||
export const ALL_PROVIDERS: readonly ProviderConfig[] = [
|
||||
codingPlanProvider,
|
||||
tokenPlanProvider,
|
||||
|
|
|
|||
|
|
@ -33,16 +33,23 @@ describe('no-AK integration CI wiring', () => {
|
|||
|
||||
expect(packageJson.scripts[NO_AK_SCRIPT]).toBe(
|
||||
[
|
||||
'cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests',
|
||||
'cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests --maxWorkers 2',
|
||||
'./fake-openai-server.test.ts',
|
||||
'./cli/daemon-invocation-context.test.ts',
|
||||
'./cli/list_directory.test.ts',
|
||||
'./cli/qwen-serve-routes.test.ts',
|
||||
'./cli/qwen-serve-streaming.test.ts',
|
||||
'./cli/daemon-invocation-context.test.ts',
|
||||
'./sdk-typescript/abort-and-lifecycle.test.ts',
|
||||
'./sdk-typescript/permission-control.test.ts',
|
||||
'./sdk-typescript/sdk-mcp-server.test.ts',
|
||||
'./sdk-typescript/subagents.test.ts',
|
||||
'./sdk-typescript/system-control.test.ts',
|
||||
'./sdk-typescript/tool-control.test.ts',
|
||||
].join(' '),
|
||||
);
|
||||
});
|
||||
|
||||
it('runs the no-AK integration script in the Ubuntu gate only', () => {
|
||||
it('runs the no-AK integration script in the required Linux gate only', () => {
|
||||
const workflow = readFileSync(
|
||||
path.join(ROOT, '.github/workflows/ci.yml'),
|
||||
'utf8',
|
||||
|
|
@ -50,13 +57,68 @@ describe('no-AK integration CI wiring', () => {
|
|||
const ubuntuJob = getWorkflowJob(workflow, 'test');
|
||||
const macosJob = getWorkflowJob(workflow, 'test_macos');
|
||||
const windowsJob = getWorkflowJob(workflow, 'test_windows');
|
||||
const permissionsIndex = workflow.indexOf('\npermissions:');
|
||||
expect(permissionsIndex).toBeGreaterThan(0);
|
||||
const workflowTriggers = workflow.slice(0, permissionsIndex);
|
||||
const gateStepMarker =
|
||||
" - name: 'Run required no-AK integration gate'";
|
||||
const gateStepStart = ubuntuJob.indexOf(gateStepMarker);
|
||||
expect(gateStepStart).toBeGreaterThanOrEqual(0);
|
||||
const nextStepIndex = ubuntuJob.indexOf(
|
||||
'\n - name:',
|
||||
gateStepStart + gateStepMarker.length,
|
||||
);
|
||||
expect(nextStepIndex).toBeGreaterThan(0);
|
||||
const gateStep = ubuntuJob.slice(gateStepStart, nextStepIndex);
|
||||
|
||||
expect(workflow).not.toContain(' integration_no_ak:');
|
||||
expect(workflow.split(`npm run ${NO_AK_SCRIPT}`).length - 1).toBe(1);
|
||||
expect(workflowTriggers).toContain('\n pull_request:\n');
|
||||
expect(workflowTriggers).toContain('\n merge_group:\n');
|
||||
|
||||
expect(ubuntuJob).toContain("name: 'Run no-AK integration smoke tests'");
|
||||
expect(ubuntuJob).toContain("github.event_name == 'pull_request'");
|
||||
expect(ubuntuJob).toContain(`npm run ${NO_AK_SCRIPT}`);
|
||||
expect(gateStep).toContain(
|
||||
"(github.event_name == 'pull_request' || github.event_name == 'merge_group')",
|
||||
);
|
||||
expect(gateStep).toContain(`npm run ${NO_AK_SCRIPT}`);
|
||||
expect(gateStep).toContain(
|
||||
"QWEN_HOME: '${{ runner.temp }}/qwen-no-ak-home/.qwen'",
|
||||
);
|
||||
expect(gateStep).toContain('timeout-minutes: 20');
|
||||
expect(gateStep).toContain(
|
||||
"\n HOME: '${{ runner.temp }}/qwen-no-ak-home'",
|
||||
);
|
||||
expect(gateStep).toContain(
|
||||
"\n USERPROFILE: '${{ runner.temp }}/qwen-no-ak-home'",
|
||||
);
|
||||
for (const key of [
|
||||
'API_KEY',
|
||||
'ANTHROPIC_API_KEY',
|
||||
'ANTHROPIC_BASE_URL',
|
||||
'ANTHROPIC_MODEL',
|
||||
'BAILIAN_CODING_PLAN_API_KEY',
|
||||
'BAILIAN_TOKEN_PLAN_API_KEY',
|
||||
'DASHSCOPE_API_KEY',
|
||||
'DEEPSEEK_API_KEY',
|
||||
'GEMINI_API_KEY',
|
||||
'GEMINI_MODEL',
|
||||
'GOOGLE_API_KEY',
|
||||
'GOOGLE_MODEL',
|
||||
'IDEALAB_API_KEY',
|
||||
'MINIMAX_API_KEY',
|
||||
'MODELSCOPE_API_KEY',
|
||||
'OPENAI_API_KEY',
|
||||
'OPENAI_BASE_URL',
|
||||
'OPENAI_MODEL',
|
||||
'OPENROUTER_API_KEY',
|
||||
'QWEN_API_KEY',
|
||||
'QWEN_DEFAULT_AUTH_TYPE',
|
||||
'QWEN_MODEL',
|
||||
'REQUESTY_API_KEY',
|
||||
'XAI_API_KEY',
|
||||
'ZAI_API_KEY',
|
||||
]) {
|
||||
expect(gateStep).toContain(`\n ${key}: ''`);
|
||||
}
|
||||
expect(ubuntuJob).not.toContain('secrets.OPENAI_API_KEY');
|
||||
expect(ubuntuJob).not.toContain('secrets.OPENAI_BASE_URL');
|
||||
expect(ubuntuJob).not.toContain('secrets.OPENAI_MODEL');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue