Fix unguarded codequalityReportsComparer.status access and nil pipeline guard

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

Summary

When a merge request has no diff_head_pipeline, the CodequalityReportsComparerResolver returns null (per its null: true contract). However, the frontend Apollo update handler in app/assets/javascripts/diffs/components/app.vue accesses codequalityReportsComparer.status without optional chaining in the stop-polling condition, which will throw a TypeError if the field is null.

Details

In app.vue, the stop-polling condition reads:

if (
  (sastReport?.status === FINDINGS_STATUS_PARSED || !this.sastReportAvailable) &&
  (!this.codequalityReportAvailable ||
    codequalityReportsComparer.status === FINDINGS_STATUS_PARSED)  // ← unguarded
) {
  this.$apollo.queries.getMRCodequalityAndSecurityReports.stopPolling();
}

sastReport?.status is consistently protected with ?., but codequalityReportsComparer.status is not. If codequalityReportAvailable is true and the GraphQL field returns null (e.g. MR has no pipeline), this throws:

TypeError: Cannot read properties of null (reading 'status')

Note: codequalityReportsComparer?.report?.newErrors (a few lines below) is already correctly guarded — only the stop-polling access is missing the ?..

Fix

Add optional chaining to the unguarded access:

// Before
codequalityReportsComparer.status === FINDINGS_STATUS_PARSED

// After
codequalityReportsComparer?.status === FINDINGS_STATUS_PARSED

When null, the expression evaluates to false, keeping polling alive rather than throwing — consistent with the behaviour for sastReport.

Also add a backend nil-pipeline guard to CodequalityReportsComparerResolver (and a corresponding spec) so that authorize!(nil) is never reached when diff_head_pipeline is absent:

def resolve
  return unless object.diff_head_pipeline

  authorize!(object.diff_head_pipeline)
  object.compare_codequality_reports
end

Context

Discovered while reviewing c708f68d, which originally included both the backend guard and the frontend fix but they were separated out for a cleaner follow-up.

Related: #458219 (closed)

Edited by 🤖 GitLab Bot 🤖