qwen-code/packages/web-shell/client/components/artifacts/codeReviewContract.test.ts
Shaojin Wen 7dfc554dff
feat(review): Add structured Web Shell review results (#8402)
* feat(review): add Web Shell review artifacts

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(web-shell): add code review artifact visual scenario (#8402)

* fix(review): address Web Shell review artifact feedback (#8402)

* save-artifact: document why paths resolve against the daemon workspace
  root (QWEN_CODE_PROJECT_DIR) instead of cwd, and cover the relative-path
  form the skill documents with a test where the two roots differ.
* CLI/renderer contract: the renderer hand-duplicates the findings
  vocabulary and fails closed on unknown values, so name the renderer as a
  second consumer beside the CLI's lists and check in a contract fixture
  generated through the real pipeline (validateFindings -> buildReport ->
  save-artifact) that exercises every source, severity, confidence and
  outcome. Exporting the vocabulary through the SDK stays deferred: it is a
  public cross-package API change beyond this PR's seam.
* resolve-anchors now validates `line` exactly like `findings` does
  (positive safe integer); the two validators in one pipeline no longer
  disagree. Note: an in-flight `.qwen/tmp` findings file carrying `line: 0`
  fails where it previously did not.
* The renderer validates markdownReportPath (relative, no ".." segments,
  .md suffix) before it becomes a readWorkspaceFile call, resets the
  severity/confidence filters when switching artifacts, and surfaces
  heldByMeasurement so a nonzero Held count is attributable.
* save-artifact refuses low effort structurally (choices and library guard)
  instead of by prose, stats the Markdown report before reading it so a
  directory reports "not a file", and the component no longer shadows the
  DOM `document` global.
* The case-insensitive alias test now skips visibly on case-sensitive
  filesystems instead of passing vacuously.
* Comment the kept `turnOutputs.review` key and document the JSON
  companion in the user docs.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(review): address second Web Shell review artifact feedback round (#8402)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-03 16:08:13 +00:00

97 lines
3.6 KiB
TypeScript

/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
// The CLI/renderer contract: the fixture is genuine `qwen review save-artifact`
// output, generated through `buildReport(validateFindings(...))` — not a
// hand-written copy. If the CLI's document shape or vocabulary changes,
// regenerate the fixture and bring this parser along.
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { parseCodeReviewDocument } from './CodeReviewArtifactDetail';
const fixture = readFileSync(
fileURLToPath(
new URL('./__fixtures__/code-review-artifact-v1.json', import.meta.url),
),
'utf8',
);
describe('code review artifact contract', () => {
it('parses the CLI-generated document without losing fields', () => {
const reviewDocument = parseCodeReviewDocument(fixture);
expect(reviewDocument.schemaVersion).toBe(1);
expect(reviewDocument.target).toBe('pr-8402');
expect(reviewDocument.effort).toBe('high');
// The parser validates only what the renderer displays; persisted-but-
// unrendered fields (downgraded, outcomesRecorded, byOutcome, ...) pass
// through unchecked and are asserted nowhere on purpose.
expect(reviewDocument.verdict).toMatchObject({
event: 'COMMENT',
baseEvent: 'REQUEST_CHANGES',
cappedBy: ['no release-blocker evidence'],
verdictLine: 'Verdict: Comment — Request changes was downgraded',
});
expect(reviewDocument.markdownReportPath).toBe(
'.qwen/reviews/contract-v1.md',
);
// The fixture exercises the whole vocabulary — every source, severity,
// confidence and outcome the CLI can canonicalize — so this parse proves
// the renderer accepts all of it.
const findings = reviewDocument.findings;
expect(findings.map((finding) => finding.id)).toEqual([
'f-critical-review',
'f-suggestion-build',
'f-suggestion-test-held',
'f-suggestion-lint',
'f-nice-probe',
]);
expect(new Set(findings.map((finding) => finding.source))).toEqual(
new Set(['review', 'build', 'test', 'probe', 'lint']),
);
expect(new Set(findings.map((finding) => finding.severity))).toEqual(
new Set(['Critical', 'Suggestion', 'Nice to have']),
);
expect(new Set(findings.map((finding) => finding.confidence))).toEqual(
new Set(['high', 'low']),
);
expect(new Set(findings.map((finding) => finding.outcome))).toEqual(
new Set(['fixed', 'skipped', 'no_change_needed']),
);
expect(reviewDocument.counts).toEqual({
total: 5,
bySeverity: { Critical: 1, Suggestion: 3, 'Nice to have': 1 },
byConfidence: { high: 4, low: 1 },
held: 1,
});
const held = findings.find((f) => f.id === 'f-suggestion-test-held');
expect(held?.heldByMeasurement).toEqual({
file: 'packages/cli/src/ui/pagination.test.ts',
});
const aggregate = findings.find((f) => f.id === 'f-suggestion-lint');
expect(aggregate?.locations).toHaveLength(2);
});
it('fails closed on a vocabulary value the renderer does not know yet', () => {
// The drift mode the contract guards: the CLI adds a value the renderer
// copy has not been taught, and documents carrying it refuse to render
// until the renderer is updated with it.
const drifted = JSON.parse(fixture) as {
findings: Array<{ source: string }>;
};
drifted.findings[0]!.source = 'typecheck';
expect(() => parseCodeReviewDocument(JSON.stringify(drifted))).toThrow(
'findings[0].source has an unsupported value.',
);
});
});