Reject mismatched timestamps when creating a dismissed vulnerability

🤖 AI wrote this code from my instructions.

Unreviewed AI output often lands on reviewers and costs them time.
This MR is human verified: I read the full diff and ran the tests myself, because your time is as valuable as mine.

What does this MR do and why?

This is a bug fix. Requests that currently succeed will start returning an error: creating a vulnerability with state: "dismissed" and a confirmedAt or resolvedAt timestamp.

Vulnerabilities::ManuallyCreateService#match_state_fields_with_state rejects a timestamp that does not belong to the requested state. It had branches for detected, confirmed, and resolved, but none for dismissed. A create with state: "dismissed" plus confirmedAt or resolvedAt passed validation.

The intent was already in the code. The class constants state the rule, for example "confirmed_at can only be set when state is confirmed". The sibling method ensure_timestamps! already lists dismissed among the states that own a timestamp, using %w[confirmed resolved dismissed]. Dismissed was missed in the validation, not excluded on purpose.

The gap has a real effect. Mutations::Vulnerabilities::Create exposes state, confirmedAt, resolvedAt, and dismissedAt as independent optional arguments and does not cross-validate them. CreateServiceBase#initialize_vulnerability copies the timestamps onto the record and sets confirmed_by or resolved_by only when the state matches. The result: a saved vulnerability with state=dismissed, resolved_at set, and resolved_by nil. No model validation catches it.

The fix adds dismissed to the validation. Adding a fourth when branch pushed the method past the Metrics/CyclomaticComplexity limit (14 against a limit of 13). Instead of disabling the cop, the four near-identical branches became a state-to-timestamp table with a loop. The table is iterated as confirmed_at, resolved_at, dismissed_at, which matches the old per-branch order for every state, so the order of returned error messages is unchanged. An unrecognised state still returns nil, as before.

Two new specs cover state: dismissed with confirmed_at, and with resolved_at.

References

None.

How to set up and validate locally

bundle exec rspec ee/spec/services/vulnerabilities/manually_create_service_spec.rb

43 examples, all passing. This file also covers the existing detected, confirmed, and resolved cases, so the reshaped method is covered by tests that predate this change.

On master, before this change, both new examples fail with expected: falsey value, because the service returns success for a dismissed vulnerability carrying a mismatched timestamp.

ee/spec/graphql/mutations/vulnerabilities/create_spec.rb and its request spec show 24 examples with 13 failures both with and without this change. Those failures are pre-existing and unrelated.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Merge request reports

Loading
Loading