Class: SpecGuard::RSpec::Transport::Result

Inherits:
Struct
  • Object
show all
Defined in:
lib/specguard/rspec/transport.rb

Overview

What the ingest endpoint said, in the one form the caller has to handle.

outcome is one of:

:success   a 2xx. `code` carries it (202 in the happy path).
:rejected  a non-2xx. `code` carries it; nothing was raised.
:failed    an exception was raised. `error` carries it.

reasons is the refusal in the platform's own words — the strings Api::BaseController#render_bad_request puts on the wire, each naming one offending spec by index, file and line. It is populated only on :rejected, and only when the body actually said something this class could read; a proxy's HTML 502 leaves it nil and the reader gets exactly what they got before.

body is the mirror of that on the accepting side: the 202 document Api::V1::IngestsController renders — test_run_id, total_specs, annotated_specs, annotated_ratio, embedding_status — parsed, and populated only on :success. It was discarded until IngestCLI needed to say which run a re-delivered line landed on, and it is added rather than substituted: nothing that read a Result before reads a different one now.

It degrades to nil on exactly the terms reasons does, and for the same reason turned around: a 202 whose body will not parse is still an acceptance, and relabelling it would tell the operator something untrue about a run the platform has already stored. See #test_run_id.

Constant Summary collapse

ADVICE =

The status codes worth spelling out, because each implies a different thing for the reader to do. A 401 means "rotate or fix the key"; a 400 means "the payload this gem built was refused", which is a bug report and not a credentials problem. Printing a bare number would leave a CI operator to guess which of the two they are looking at.

{
  400 => "the endpoint rejected the payload",
  401 => "the API key was not accepted",
  403 => "this API key may not write to that repository",
  404 => "no ingest endpoint at that URL — check SPECGUARD_ENDPOINT",
  413 => "the payload was too large for the endpoint",
  429 => "rate limited by the endpoint"
}.freeze
MAX_RENDERED_REASONS =

How many of the platform's reasons are spelled out, with an and N more tail standing in for the rest.

A cap rather than the whole list, because the list is unbounded at its source: Ingest::Payload appends one error per bad spec, so a systemic client bug on a 20,000-example suite comes back with ~20,000 strings. Three is enough to see the shape of the failure — they are near-identical when the cause is systemic — and the count tells the reader how widespread it is.

3
MAX_REASONS_LENGTH =

Ceiling on the spelled-out reasons, before the and N more tail, which is appended afterwards so the count can never be the thing that gets truncated away.

300

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#bodyObject

Returns the value of attribute body

Returns:

  • (Object)

    the current value of body



170
171
172
# File 'lib/specguard/rspec/transport.rb', line 170

def body
  @body
end

#codeObject

Returns the value of attribute code

Returns:

  • (Object)

    the current value of code



170
171
172
# File 'lib/specguard/rspec/transport.rb', line 170

def code
  @code
end

#errorObject

Returns the value of attribute error

Returns:

  • (Object)

    the current value of error



170
171
172
# File 'lib/specguard/rspec/transport.rb', line 170

def error
  @error
end

#outcomeObject

Returns the value of attribute outcome

Returns:

  • (Object)

    the current value of outcome



170
171
172
# File 'lib/specguard/rspec/transport.rb', line 170

def outcome
  @outcome
end

#reasonsObject

Returns the value of attribute reasons

Returns:

  • (Object)

    the current value of reasons



170
171
172
# File 'lib/specguard/rspec/transport.rb', line 170

def reasons
  @reasons
end

Instance Method Details

#reasonString?

A single clause naming what went wrong, for the one stderr line a run is allowed. nil on success, because there is nothing to say.

Returns:

  • (String, nil)


224
225
226
227
228
229
230
# File 'lib/specguard/rspec/transport.rb', line 224

def reason
  case outcome
  when :success then nil
  when :rejected then [+"HTTP #{code}", ADVICE[code], rendered_reasons].compact.join("")
  else "#{error.class}: #{error.message}"
  end
end

#success?Boolean

Returns:

  • (Boolean)


201
# File 'lib/specguard/rspec/transport.rb', line 201

def success? = outcome == :success

#test_run_idString?

The id of the TestRun this delivery landed on, as the endpoint reported it — nil whenever that cannot be said honestly: a refusal, an exception, a 202 with an unreadable body, or one whose test_run_id is not a scalar.

Returned as a String so two deliveries can be compared without caring whether the platform spells an id as a UUID or a number. Two accepted lines answering with the same value landed on the same row; that is the only claim about folding this class can support, and IngestCLI makes exactly that one.

Returns:

  • (String, nil)


215
216
217
218
# File 'lib/specguard/rspec/transport.rb', line 215

def test_run_id
  value = body.is_a?(Hash) ? body["test_run_id"] : nil
  value.is_a?(String) || value.is_a?(Numeric) ? value.to_s : nil
end