mirror of
https://github.com/Tencent/teamai-cli.git
synced 2026-10-02 03:14:40 +08:00
* fix(review): print review reject/apply output in English (#836) teamai review --reject and --apply printed Chinese strings in their console output: 已拒绝 for rejected, 应用失败 for apply failed, and Chinese error reasons (缺失, 为空, 不存在, 不支持自动应用). Issue #836 established that CLI output must be English, and #840 fixed the same class of bug in cache-cmd and import-local; review-cmd was missed. Translate the six user-facing strings in applyOne's error reasons and reviewCmd's reject/apply messages. Code comments are left as-is — they are not user-facing. Add three tests that assert the English text and no CJK characters in the output; all three fail before this fix. * fix(review): update existing test to expect English output The domain-drift apply test in review-cmd.test.ts still asserted the Chinese string '不支持' which was translated in the previous commit. Update the assertion to match the English output.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
|
||||
// Stub chalk so the test sees raw text, not ANSI codes.
|
||||
vi.mock('chalk', () => ({
|
||||
default: {
|
||||
yellow: (s: string) => s,
|
||||
red: (s: string) => s,
|
||||
green: (s: string) => s,
|
||||
cyan: (s: string) => s,
|
||||
dim: (s: string) => s,
|
||||
bold: (s: string) => s,
|
||||
},
|
||||
}));
|
||||
|
||||
import { reviewCmd } from '../review-cmd.js';
|
||||
|
||||
const CJK = /[\u3000-\u303f\u4e00-\u9fff\uff00-\uffef]/;
|
||||
|
||||
let tmpDir: string;
|
||||
let consoleSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'teamai-review-'));
|
||||
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleSpy.mockRestore();
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function seedReviewItem(item: Record<string, unknown>): void {
|
||||
const reviewPath = path.join(tmpDir, '.teamai', 'pending-review.jsonl');
|
||||
fs.mkdirSync(path.dirname(reviewPath), { recursive: true });
|
||||
fs.appendFileSync(reviewPath, JSON.stringify(item) + '\n');
|
||||
}
|
||||
|
||||
describe('review-cmd English output (#836)', () => {
|
||||
it('reject prints English, not Chinese', async () => {
|
||||
seedReviewItem({
|
||||
id: 'test-item',
|
||||
ts: new Date().toISOString(),
|
||||
kind: 'codebase-section',
|
||||
source: 'test',
|
||||
target: { file: 'x.md', section: 'a' },
|
||||
payload: { content: 'body' },
|
||||
risk: 'low',
|
||||
});
|
||||
|
||||
const origCwd = process.cwd();
|
||||
process.chdir(tmpDir);
|
||||
try {
|
||||
await reviewCmd({ idArg: 'test-item', reject: true });
|
||||
} finally {
|
||||
process.chdir(origCwd);
|
||||
}
|
||||
|
||||
const output = consoleSpy.mock.calls.map((c) => c.join(' ')).join('\n');
|
||||
expect(output).toContain('rejected');
|
||||
expect(output).not.toMatch(CJK);
|
||||
});
|
||||
|
||||
it('apply-failure reason is English when target.section is missing', async () => {
|
||||
seedReviewItem({
|
||||
id: 'no-section',
|
||||
ts: new Date().toISOString(),
|
||||
kind: 'codebase-section',
|
||||
source: 'test',
|
||||
target: { file: 'x.md' },
|
||||
payload: { content: 'body' },
|
||||
risk: 'low',
|
||||
});
|
||||
|
||||
const origCwd = process.cwd();
|
||||
process.chdir(tmpDir);
|
||||
try {
|
||||
await reviewCmd({ idArg: 'no-section', apply: true });
|
||||
} finally {
|
||||
process.chdir(origCwd);
|
||||
}
|
||||
|
||||
const output = consoleSpy.mock.calls.map((c) => c.join(' ')).join('\n');
|
||||
expect(output).toContain('apply failed');
|
||||
expect(output).toContain('target.section is missing');
|
||||
expect(output).not.toMatch(CJK);
|
||||
});
|
||||
|
||||
it('apply-failure reason is English when payload.content is empty', async () => {
|
||||
const targetFile = path.join(tmpDir, 'target.md');
|
||||
fs.writeFileSync(targetFile, '## Section\nBody\n');
|
||||
seedReviewItem({
|
||||
id: 'empty-payload',
|
||||
ts: new Date().toISOString(),
|
||||
kind: 'codebase-section',
|
||||
source: 'test',
|
||||
target: { file: targetFile, section: 's' },
|
||||
payload: {},
|
||||
risk: 'low',
|
||||
});
|
||||
|
||||
const origCwd = process.cwd();
|
||||
process.chdir(tmpDir);
|
||||
try {
|
||||
await reviewCmd({ idArg: 'empty-payload', apply: true });
|
||||
} finally {
|
||||
process.chdir(origCwd);
|
||||
}
|
||||
|
||||
const output = consoleSpy.mock.calls.map((c) => c.join(' ')).join('\n');
|
||||
expect(output).toContain('apply failed');
|
||||
expect(output).toContain('payload.content is empty');
|
||||
expect(output).not.toMatch(CJK);
|
||||
});
|
||||
});
|
||||
@@ -142,7 +142,7 @@ describe('review-cmd', () => {
|
||||
expect(removePendingReview).not.toHaveBeenCalled();
|
||||
|
||||
const output = consoleSpy.mock.calls.map((c) => c.join(' ')).join('\n');
|
||||
expect(output).toContain('不支持');
|
||||
expect(output).toContain('does not support auto-apply');
|
||||
});
|
||||
|
||||
// ── reject 模式 ───────────────────────────────────────
|
||||
|
||||
+6
-6
@@ -125,23 +125,23 @@ async function applyOne(
|
||||
item: PendingReviewItem,
|
||||
): Promise<{ ok: boolean; reason?: string }> {
|
||||
if (item.kind !== 'codebase-section') {
|
||||
return { ok: false, reason: `kind ${item.kind} 不支持自动应用,请人工处理` };
|
||||
return { ok: false, reason: `kind ${item.kind} does not support auto-apply; please handle manually` };
|
||||
}
|
||||
|
||||
const { file, section } = item.target;
|
||||
if (!section) {
|
||||
return { ok: false, reason: 'target.section 缺失' };
|
||||
return { ok: false, reason: 'target.section is missing' };
|
||||
}
|
||||
|
||||
const filePath = path.isAbsolute(file) ? file : path.join(cwd, file);
|
||||
if (!await fs.pathExists(filePath)) {
|
||||
return { ok: false, reason: `目标文件不存在:${filePath}` };
|
||||
return { ok: false, reason: `target file not found: ${filePath}` };
|
||||
}
|
||||
|
||||
const oldMd = await fs.readFile(filePath, 'utf8');
|
||||
const body = String(item.payload['content'] ?? '');
|
||||
if (!body) {
|
||||
return { ok: false, reason: 'payload.content 为空' };
|
||||
return { ok: false, reason: 'payload.content is empty' };
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -236,7 +236,7 @@ export async function reviewCmd(opts: ReviewCmdOptions): Promise<void> {
|
||||
console.log(JSON.stringify({ ok: true, action: 'reject', id: idArg }));
|
||||
return;
|
||||
}
|
||||
console.log(chalk.yellow(`[review] 已拒绝:${idArg}`));
|
||||
console.log(chalk.yellow(`[review] rejected: ${idArg}`));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ export async function reviewCmd(opts: ReviewCmdOptions): Promise<void> {
|
||||
if (result.ok) {
|
||||
console.log(chalk.green(`[review] applied: ${idArg} → ${item.target.file}`));
|
||||
} else {
|
||||
console.log(chalk.red(`[review] 应用失败:${idArg} — ${result.reason}`));
|
||||
console.log(chalk.red(`[review] apply failed: ${idArg} — ${result.reason}`));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user