Skip to content

Add Allure upload command support#50

Draft
satvik007 wants to merge 1 commit intomainfrom
feat/allure-upload-support
Draft

Add Allure upload command support#50
satvik007 wants to merge 1 commit intomainfrom
feat/allure-upload-support

Conversation

@satvik007
Copy link
Collaborator

Summary

  • Add allure-upload command support with a new directory-based Allure parser (*-result.json) including status/folder/message mapping and attachment resolution.
  • Wire Allure into command handling/help/guidance, including unmatched-test guidance and force-aware handling of invalid Allure result files.
  • Add comprehensive Allure unit/integration coverage plus fixture directories, and create AGENTS.md as a symlink to CLAUDE.md.

Test plan

  • npm test
  • npm run check
  • Optional manual smoke: qasphere allure-upload --run-url <run-url> ./allure-results

Made with Cursor

Introduce a directory-based Allure parser and wire it into the upload pipeline so users can upload Allure 2 results with TMS-link or marker-based matching. Extend CLI, tests, and docs for allure-upload, support force-aware skipping of invalid Allure files, and add AGENTS.md as a symlink to CLAUDE.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link

claude bot commented Feb 10, 2026

Claude finished @satvik007's task in 5m 6s —— View job


Review Complete

  • Read review instructions
  • Gather PR context and diff
  • Launch specialized review subagents (code quality, security, performance, test coverage)
  • Aggregate findings and post review
  • Clean up

Submitted a review with 7 inline comments covering:

  • Security: Path traversal risk via Allure attachment source field (medium)
  • Code quality: Marker regex mismatch with codebase (\d+ vs \d{3,}), missing directory validation, CLAUDE.md numbering regression, Parser type semantic overload, stdout/stderr mapping imprecision
  • Test coverage: 6 edge case gaps compared to JUnit/Playwright test parity

No critical/high severity issues found. No performance regressions introduced.

@gemini-code-assist
Copy link

Summary of Changes

Hello @satvik007, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the QAS CLI's capabilities by integrating native support for Allure test reports. Users can now upload Allure results directories, leveraging a new parser that handles status mapping, folder organization, and attachment resolution. The update also includes enhanced error handling for invalid Allure files and comprehensive documentation, making the CLI more versatile for diverse test automation workflows.

Highlights

  • New Allure Upload Command: Introduced a new allure-upload command to the QAS CLI, enabling direct upload of Allure test results to QA Sphere.
  • Directory-Based Allure Parser: Implemented a dedicated parser for Allure 2 JSON results (*-result.json files), supporting status mapping, folder extraction, message processing, and attachment resolution.
  • Flexible Test Case Linking: Allure results can now link to QA Sphere test cases via TMS links (recommended), TMS link names, or markers embedded directly in test names.
  • Robust Error Handling for Allure Results: Added support for the --force option with allure-upload, allowing the CLI to skip malformed or schema-invalid Allure result files and continue processing valid ones.
  • Comprehensive Documentation and CLI Guidance: Updated README.md and CLAUDE.md to reflect Allure support, including new command examples and guidance for resolving unmatched Allure test cases.
  • Expanded Test Coverage: Added new unit tests for the Allure parser and integrated Allure into existing end-to-end upload integration tests.
Changelog
  • AGENTS.md
    • Added a symlink to CLAUDE.md.
  • CLAUDE.md
    • Updated project overview to include Allure results directories as a supported input type.
    • Modified CLI framework description to list three commands, including allure-upload.
    • Added Allure parser to the core upload pipeline documentation.
    • Included allure-parsing.spec.ts in the list of test files.
  • README.md
    • Updated the tool's description to mention Allure result directories.
    • Corrected the example format for QAS_URL.
    • Updated the 'Commands' section to include allure-upload.
    • Added a description for input paths, differentiating between files and directories for various commands.
    • Added new examples for allure-upload, including uploading results from a directory and using the --force option.
    • Updated 'Test Report Requirements' to include Allure as a supported report type and detailed Allure-specific test case linking methods.
  • package-lock.json
    • Updated the package version from 0.4.4 to 0.4.5.
  • src/commands/main.ts
    • Registered the new allure-upload command with the Yargs CLI setup.
  • src/commands/resultUpload.ts
    • Extended commandTypeDisplayStrings to include 'Allure'.
    • Added commandTypeInputKinds to specify whether a command expects 'files' or 'directories'.
    • Added commandTypeExampleInputs to provide example input paths for each command type.
    • Modified the describe getter to dynamically use commandTypeInputKinds for better command descriptions.
    • Added a positional argument for 'files' that dynamically describes input as files or directories based on the command type.
    • Updated argv.example calls to use commandTypeExampleInputs for more accurate command examples.
  • src/tests/allure-parsing.spec.ts
    • Added a new test file containing unit tests for the Allure parser, covering marker extraction, status mapping, folder priority, attachment handling, and malformed file handling.
  • src/tests/fixtures/allure/empty-tsuite/001-result.json
    • Added a new Allure result fixture for testing empty test suites.
  • src/tests/fixtures/allure/empty-tsuite/002-container.json
    • Added a new Allure container fixture for testing empty test suites.
  • src/tests/fixtures/allure/invalid-results/001-result.json
    • Added a new Allure result fixture for testing partial directories.
  • src/tests/fixtures/allure/invalid-results/003-result.json
    • Added a new Allure result fixture for testing schema-invalid files.
  • src/tests/fixtures/allure/matching-tcases/001-result.json
    • Added a new Allure result fixture with TMS link and suite label.
  • src/tests/fixtures/allure/matching-tcases/002-result.json
    • Added a new Allure result fixture with TMS link name fallback and parentSuite label.
  • src/tests/fixtures/allure/matching-tcases/003-result.json
    • Added a new Allure result fixture with marker in name and feature label.
  • src/tests/fixtures/allure/matching-tcases/004-result.json
    • Added a new Allure result fixture with failed status, status details, and package label.
  • src/tests/fixtures/allure/matching-tcases/005-result.json
    • Added a new Allure result fixture with unknown status and no labels.
  • src/tests/fixtures/allure/matching-tcases/006-container.json
    • Added a new Allure container fixture for matching test cases.
  • src/tests/fixtures/allure/matching-tcases/legacy-testsuite.xml
    • Added a new legacy Allure 1 XML fixture to ensure it's ignored by the new parser.
  • src/tests/fixtures/allure/matching-tcases/shared-attachment.txt
    • Added a new shared attachment fixture for Allure tests.
  • src/tests/fixtures/allure/missing-attachments/001-result.json
    • Added a new Allure result fixture with an existing attachment.
  • src/tests/fixtures/allure/missing-attachments/002-result.json
    • Added a new Allure result fixture with an existing attachment.
  • src/tests/fixtures/allure/missing-attachments/003-result.json
    • Added a new Allure result fixture with an existing attachment.
  • src/tests/fixtures/allure/missing-attachments/004-result.json
    • Added a new Allure result fixture with an existing attachment.
  • src/tests/fixtures/allure/missing-attachments/005-result.json
    • Added a new Allure result fixture with a missing attachment.
  • src/tests/fixtures/allure/missing-attachments/existing-attachment.txt
    • Added a new existing attachment fixture for Allure tests.
  • src/tests/fixtures/allure/missing-tcases/001-result.json
    • Added a new Allure result fixture for testing missing test cases.
  • src/tests/fixtures/allure/missing-tcases/002-result.json
    • Added a new Allure result fixture for testing missing test cases.
  • src/tests/fixtures/allure/missing-tcases/003-result.json
    • Added a new Allure result fixture for testing missing test cases.
  • src/tests/fixtures/allure/missing-tcases/004-result.json
    • Added a new Allure result fixture for testing missing test cases.
  • src/tests/fixtures/allure/missing-tcases/005-result.json
    • Added a new Allure result fixture for testing missing test cases.
  • src/tests/fixtures/allure/without-markers/001-result.json
    • Added a new Allure result fixture without markers.
  • src/tests/fixtures/allure/without-markers/002-result.json
    • Added a new Allure result fixture without markers.
  • src/tests/fixtures/allure/without-markers/003-result.json
    • Added a new Allure result fixture without markers.
  • src/tests/result-upload.spec.ts
    • Introduced TestFileType interface and fixtureInputPath helper for consistent test fixture handling.
    • Extended the fileTypes array to fileTypesWithAllure to include Allure for integration tests.
    • Updated existing integration tests to dynamically use fixtureInputPath for test report paths, supporting both files and directories.
    • Added a new test suite specifically for Allure invalid result file handling, verifying behavior with and without the --force option.
  • src/utils/result-upload/ResultUploadCommandHandler.ts
    • Extended UploadCommandType to include allure-upload.
    • Imported parseAllureResults and added it to the commandTypeParsers map.
    • Added allowPartialParse to ParserOptions to support skipping invalid files.
    • Modified the Parser type definition to clarify that data can be a directory path for directory-based parsers.
    • Updated the parseFiles method to conditionally read file content or use the file path directly based on the command type (e.g., Allure uses directory path).
    • Passed allowPartialParse (derived from the --force argument) to the parser options.
    • Refactored execRegexWithPriority to use String.prototype.match for regex execution.
  • src/utils/result-upload/ResultUploader.ts
    • Added a new private method printAllureGuidance to provide specific instructions for resolving unmatched Allure test cases.
    • Integrated printAllureGuidance into printUnmatchedGuidance to display relevant help messages for allure-upload.
  • src/utils/result-upload/allureParser.ts
    • Added a new file containing the parseAllureResults function.
    • Defined Zod schemas for Allure result structures (status, status details, attachments, labels, links, parameters).
    • Implemented logic for mapping Allure statuses to QA Sphere result statuses.
    • Included functions for extracting folder information based on Allure labels (suite, parentSuite, feature, package).
    • Developed buildMessage to format status details (stdout/stderr) based on skip options.
    • Created extractMarker to prioritize marker extraction from TMS links (URL, then name) and then from the test name itself.
    • Added error handling for malformed or schema-invalid Allure JSON files, with an option to allow partial parsing.
Activity
  • The author has performed unit tests (npm test) and code style checks (npm run check).
  • Optional manual smoke tests for the allure-upload command were suggested by the author.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Good implementation that follows existing codebase patterns well. The Allure parser is well-structured with proper Zod validation, comprehensive status mapping, and a clear marker extraction priority chain (TMS URL → TMS link name → test name).

Key items to address:

  1. Path traversal via attachment source field (medium security) — Allure result JSON files can specify arbitrary file paths in attachments[].source, which are read and uploaded without path validation. Relevant in CI/CD where results may come from untrusted sources.

  2. Marker regex mismatchmarkerRegex accepts \d+ (any digits) while the rest of the codebase uses \d{3,} (≥3 digits), risking false-positive matches on text like "step-1".

  3. Missing directory validationreaddirSync on a non-existent path produces a raw Node.js error without user-friendly context. An empty directory silently returns zero results.

  4. CLAUDE.md numbering regression — The numbered list in "Core Upload Pipeline" has item 2 rendered as item 1.

Design notes (non-blocking):

  • The Parser type's data parameter is semantically overloaded (file contents vs. directory path). The comment helps, but this is technical debt to watch.
  • The statusDetails.message → "stdout" / .trace → "stderr" mapping is imprecise relative to the CLI flag names.
  • Several test coverage gaps compared to JUnit/Playwright parity (empty dir, stop < start, asymmetric skip options, HTML escaping, no-marker case).


type AllureResult = z.infer<typeof allureResultSchema>

const markerRegex = /\b([A-Za-z0-9]{1,5})-(\d+)\b/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marker regex accepts shorter sequences than the rest of the codebase

The markerRegex here uses \d+ (any number of digits), but ResultUploadCommandHandler.detectProjectCodeFromTCaseNames and getTCaseIds use \d{3,} (at least 3 digits). While getTCaseMarker() normalizes short sequences (e.g., PRJ-1PRJ-001), the broader regex could produce false-positive matches on non-marker text (e.g., a test name containing "step-1" or "v2-5").

Consider changing to \d{3,} for consistency:

Suggested change
const markerRegex = /\b([A-Za-z0-9]{1,5})-(\d+)\b/
const markerRegex = /\b([A-Za-z0-9]{1,5})-(\d{3,})\b/

Alternatively, if matching shorter sequences is intentional (since getTCaseMarker normalizes), add a comment explaining the divergence.

attachments: [],
}) - 1

const attachmentPaths = (parsedResult.attachments || []).map((attachment) => attachment.source)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path traversal via Allure attachment source field (Medium severity)

The attachment.source field from Allure JSON result files is used directly as a file path without path traversal validation. A malicious result file could set source to ../../../../etc/passwd or an absolute path, and path.join() in the getAttachments utility would resolve the traversal segments, allowing arbitrary file reads. The file contents would then be uploaded to the QA Sphere API as attachments.

This is particularly relevant in CI/CD environments where Allure results may originate from untrusted test code or shared artifact stores.

Consider validating that resolved attachment paths remain within the expected base directory:

const resolvedPath = path.resolve(basePath, filePath)
if (!resolvedPath.startsWith(path.resolve(basePath))) {
  throw new Error(`Attachment path "${filePath}" escapes the base directory`)
}

Alternatively, add a Zod refinement on the source field to reject values containing path separators or .. segments.

attachmentBaseDirectory: string,
options: ParserOptions
): Promise<TestCaseResult[]> => {
const resultFiles = readdirSync(resultsDirectory)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No validation that the results directory exists or is readable

If resultsDirectory does not exist or is not a directory, readdirSync will throw a raw Node.js error (e.g., ENOENT: no such file or directory, scandir '/wrong/path'). Unlike the JUnit/Playwright parsers which get their error at the readFileSync call in parseFiles(), the Allure parser's error lacks user-friendly context.

Consider wrapping this in a try/catch with a descriptive error:

let resultFiles: string[]
try {
  resultFiles = readdirSync(resultsDirectory)
    .filter((f) => f.endsWith('-result.json'))
    .sort()
} catch (error) {
  throw new Error(`Failed to read Allure results directory "${resultsDirectory}": ${getErrorMessage(error)}`)
}

Also, if resultFiles is empty, consider emitting a warning like No Allure result files (*-result.json) found in directory "${resultsDirectory}" to help users who point at the wrong directory.


for (const file of this.args.files) {
const fileData = readFileSync(file).toString()
const fileData = this.type === 'allure-upload' ? file : readFileSync(file).toString()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantic overloading of the Parser type's data parameter

The Parser type uses data: string to mean "file contents" for JUnit/Playwright but "directory path" for Allure. The conditional branching here and at line 144 (this.type === 'allure-upload' ? ... : ...) creates coupling that must be kept in sync.

The comment on lines 25-26 documents this, which helps. As the number of parser types grows, consider refactoring so each parser receives a uniform input (e.g., always a path, and handles its own reading), which would eliminate these type-checking conditionals. Not blocking, but worth noting as technical debt.

Comment on lines +169 to +179
const buildMessage = (
result: AllureResult,
status: ResultStatus,
options: ParserOptions
): string => {
const statusDetails = result.statusDetails
if (!statusDetails) {
return ''
}

const includeStdout = !(status === 'passed' && options.skipStdout === 'on-success')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

statusDetails.message mapped to "stdout" and .trace mapped to "stderr" is semantically imprecise

The skipStdout option controls whether statusDetails.message (an error message) is included, and skipStderr controls statusDetails.trace (a stack trace). Neither is actually stdout or stderr. This means --skip-report-stdout on-success would suppress error messages for passing tests, which may be unintuitive.

Consider either:

  1. Always including statusDetails.message (like the Playwright parser does for errors), applying skip logic only to trace
  2. Adding a comment explaining the mapping rationale so future maintainers understand this design choice

1. **`ResultUploader`** — Handles the upload-to-run mechanics:
- Fetches test cases from the run, maps parsed results to them via marker matching
- Validates unmatched/missing test cases (respects `--force`, `--ignore-unmatched`)
- Uploads file attachments concurrently (max 10 parallel), then creates results in batches (max 50 per request)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numbered list regression: The second item in this list changed from 2. to 1. (and the sub-items lost their nested indentation). This appears to be an accidental formatting regression.

Suggested change
- Uploads file attachments concurrently (max 10 parallel), then creates results in batches (max 50 per request)
1. **`ResultUploader`** — Handles the upload-to-run mechanics:

)
})

describe('Allure parsing', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage gaps to consider:

A few edge cases tested by the existing JUnit/Playwright test suites are missing from the Allure parser tests:

  1. Empty results directory (zero *-result.json files) — the boundary condition where resultFiles is empty
  2. stop < start — exercises the timeTaken: null branch at allureParser.ts:118
  3. Failed test with skip on-success — verifies message/trace are preserved for non-passing tests (parity with JUnit/Playwright skip tests)
  4. Asymmetric skip options — one skip option on-success, the other never (e.g., skipStdout: 'on-success', skipStderr: 'never')
  5. HTML escaping / ANSI stripping — verifies <, >, & are escaped and ANSI codes are stripped in messages (parity with Playwright test)
  6. No marker found — a unit test for results with no TMS links and no marker in name, verifying the raw name is used as-is

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces comprehensive support for Allure test results, significantly expanding the CLI's capabilities by adding a new allure-upload command and implementing a dedicated Allure parser. While the integration is robust and handles various Allure features effectively, a critical Path Traversal vulnerability was identified in the Allure attachment parsing logic. This vulnerability could allow an attacker to exfiltrate sensitive files from the environment where the CLI tool is run (e.g., a CI/CD runner) by providing malicious Allure result files. It is strongly recommended to implement robust path validation for all attachment sources to address this security concern.

Comment on lines +122 to +125
const attachmentPaths = (parsedResult.attachments || []).map((attachment) => attachment.source)
attachmentsPromises.push({
index,
promise: getAttachments(attachmentPaths, attachmentBaseDirectory || resultsDirectory),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The parseAllureResults function extracts attachment source paths from the Allure result JSON and passes them directly to getAttachments without validation. Since getAttachments (and the underlying getFile function in utils.ts) uses path.join with these paths, an attacker can provide a malicious Allure result file with directory traversal sequences (e.g., ../../etc/passwd) in the source field. This allows reading arbitrary files from the system where the CLI tool is executed and uploading them as attachments to the server.

To remediate this, ensure that all attachment paths are sanitized and verified to be within the expected results directory before processing them.

Comment on lines +172 to +185
private execRegexWithPriority(pattern: string, str: string): RegExpMatchArray | null {
// Try matching at start first
const startRegex = new RegExp(`^${pattern}`)
let match = startRegex.exec(str)
let match = str.match(startRegex)
if (match) return match

// Try matching at end
const endRegex = new RegExp(`${pattern}$`)
match = endRegex.exec(str)
match = str.match(endRegex)
if (match) return match

// Fall back to matching anywhere
const anywhereRegex = new RegExp(pattern)
return anywhereRegex.exec(str)
return str.match(anywhereRegex)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The change from RegExp.prototype.exec() to String.prototype.match() for non-global regular expressions is generally acceptable as they behave similarly in this context. However, RegExp.prototype.exec() is often preferred when you need to extract capture groups and iterate over matches (though not applicable here without the g flag). While str.match(regex) is valid, sticking to regex.exec(str) would maintain consistency with the RegExpExecArray return type, which is what exec explicitly returns. The current change to RegExpMatchArray is technically correct for str.match(), but it's a subtle shift in API usage.

Suggested change
private execRegexWithPriority(pattern: string, str: string): RegExpMatchArray | null {
// Try matching at start first
const startRegex = new RegExp(`^${pattern}`)
let match = startRegex.exec(str)
let match = str.match(startRegex)
if (match) return match
// Try matching at end
const endRegex = new RegExp(`${pattern}$`)
match = endRegex.exec(str)
match = str.match(endRegex)
if (match) return match
// Fall back to matching anywhere
const anywhereRegex = new RegExp(pattern)
return anywhereRegex.exec(str)
return str.match(anywhereRegex)
private execRegexWithPriority(pattern: string, str: string): RegExpExecArray | null {
// Try matching at start first
const startRegex = new RegExp(`^${pattern}`)
let match = startRegex.exec(str)
if (match) return match
// Try matching at end
const endRegex = new RegExp(`${pattern}$`)
match = endRegex.exec(str)
if (match) return match
// Fall back to matching anywhere
const anywhereRegex = new RegExp(pattern)
return anywhereRegex.exec(str)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant