Module: GraphWeaver::Testing::Failure

Includes:
Kernel
Defined in:
lib/graph_weaver/testing/failure.rb

Overview

Canned failure clients — each produces exactly what the real transports produce, so error-handling paths are testable without a server that misbehaves on cue:

 PersonQuery.execute(id: "1", Failure.transport)   # TransportError
 PersonQuery.execute(id: "1", Failure.server(status: 502))
 PersonQuery.execute(id: "1", Failure.throttled)   # QueryError, code THROTTLED
 PersonQuery.execute(id: "1", Failure.stale_schema) # schema_stale? => true

For type mismatches, corrupt the wire with a FakeClient override: FakeClient.new(schema:, overrides: { "Person.birthday" => 123 }) casting then raises GraphWeaver::TypeError, exactly as a bad server payload would. For partial failures, see FakeClient's fail_at:.

Class Method Summary collapse

Class Method Details

.graphql(*errors, data: nil, extensions: {}) ⇒ Object

top-level GraphQL errors: strings, or hashes with message/path/ extensions; data: rides along for partial-failure envelopes



42
43
44
45
46
47
48
49
50
51
# File 'lib/graph_weaver/testing/failure.rb', line 42

def graphql(*errors, data: nil, extensions: {})
  normalized = errors.flatten.map do |error|
    error.is_a?(String) ? { "message" => error } : JSON.parse(JSON.generate(error))
  end

  response = { "errors" => normalized }
  response["data"] = data if data
  response["extensions"] = JSON.parse(JSON.generate(extensions)) unless extensions.empty?
  FailureClient.new { response }
end

.server(status: 500, body: "simulated server error") ⇒ Object

the server answered non-2xx



36
37
38
# File 'lib/graph_weaver/testing/failure.rb', line 36

def server(status: 500, body: "simulated server error")
  FailureClient.new { raise GraphWeaver::ServerError.new(status:, body:) }
end

.stale_schema(field: nil, type: nil, schema: nil, seed: nil) ⇒ Object

A validation-shaped rejection — trips schema_stale? and its regenerate hint, as if the schema changed under the module. Name the casualty explicitly, or pass schema: to sample a real type/field (as if the server just dropped it):

 Failure.stale_schema(type: "Person", field: "name")
 Failure.stale_schema(schema: MySchema)             # random real field
 Failure.stale_schema(schema: MySchema, seed: 42)   # reproducibly random


66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/graph_weaver/testing/failure.rb', line 66

def stale_schema(field: nil, type: nil, schema: nil, seed: nil)
  if schema && (field.nil? || type.nil?)
    rng = Random.new(seed || GraphWeaver::Testing.config.seed || Random.new_seed)
    candidates = schema.types.values.select do |candidate|
      candidate.kind.name == "OBJECT" && !candidate.graphql_name.start_with?("__")
    end
    chosen = candidates.sort_by(&:graphql_name).sample(random: rng)
    type ||= chosen.graphql_name
    field ||= chosen.fields.keys.sort.sample(random: rng)
  end

  graphql("Field '#{field || "someField"}' doesn't exist on type '#{type || "SomeType"}'")
end

.throttledObject



53
54
55
56
# File 'lib/graph_weaver/testing/failure.rb', line 53

def throttled
  # array-wrapped so the hash can't parse as kwargs
  graphql([{ message: "rate limited", extensions: { code: "THROTTLED" } }])
end

.transport(message = "simulated network failure", cause: SocketError) ⇒ Object

the request never reaches the server — cause preserved, like the bundled transports do



27
28
29
30
31
32
33
# File 'lib/graph_weaver/testing/failure.rb', line 27

def transport(message = "simulated network failure", cause: SocketError)
  FailureClient.new do
    raise cause, message
  rescue cause => e
    raise GraphWeaver::TransportError, e.message
  end
end