File: instrumentation_spec.rb

package info (click to toggle)
ruby-graphql 2.2.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,584 kB
  • sloc: ruby: 67,505; ansic: 1,753; yacc: 831; javascript: 331; makefile: 6
file content (207 lines) | stat: -rw-r--r-- 6,972 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# frozen_string_literal: true
require "spec_helper"

describe GraphQL::Schema do
  describe "instrumentation teardown bug" do
    # This instrumenter records that it ran,
    # or raises an error if instructed to do so
    class InstrumenterError < StandardError
      attr_reader :key
      def initialize(key)
        @key = key
        super()
      end
    end

    module LogInstrumenter
      def self.generate(context_key_sym)
        hook_method = :"#{context_key_sym}_run_hook"
        mod = Module.new

        mod.define_method(:execute_query) do |query:, &block|
          public_send(hook_method, query, "begin")
          result = nil
          begin
            result = super(query: query, &block)
          ensure
            public_send(hook_method, query, "end")
          end
          result
        end

        mod.define_method(:execute_multiplex) do |multiplex:, &block|
          public_send(hook_method, multiplex, "begin")
          result = nil
          begin
            result = super(multiplex: multiplex, &block)
          ensure
            public_send(hook_method, multiplex, "end")
          end
          result
        end

        mod.define_method(hook_method) do |unit_of_work, event_name|
          log_key = :"#{context_key_sym}_did_#{event_name}"
          error_key = :"#{context_key_sym}_should_raise_#{event_name}"
          unit_of_work.context[log_key] = true
          if unit_of_work.context[error_key]
            raise InstrumenterError.new(log_key)
          end
        end

        mod
      end
    end

    module ExecutionErrorTrace
      def execute_query(query:)
        if query.context[:raise_execution_error]
          raise GraphQL::ExecutionError, "Raised from trace execute_query"
        end
        super
      end
    end

    # This is how you might add queries from a persisted query backend

    module QueryStringTrace
      def execute_multiplex(multiplex:)
        multiplex.queries.each do |query|
          if query.context[:extra_query_string] && query.query_string.nil?
            query.query_string = query.context[:extra_query_string]
          end
        end
        super
      end
    end

    let(:query_type) {
      Class.new(GraphQL::Schema::Object) do
        graphql_name "Query"
        field :int, Integer do
          argument :value, Integer, required: false
        end

        def int(value:)
          value
        end
      end
    }

    let(:schema) {
      spec = self
      Class.new(GraphQL::Schema) do
        query(spec.query_type)
        trace_with(LogInstrumenter.generate(:second_instrumenter))
        trace_with(LogInstrumenter.generate(:first_instrumenter))
        trace_with(ExecutionErrorTrace)
        trace_with(QueryStringTrace)
      end
    }

    describe "query instrumenters" do
      it "before_query of the 2nd instrumenter does not run but after_query does" do
        context = {second_instrumenter_should_raise_begin: true}
        assert_raises InstrumenterError do
          schema.execute(" { int(value: 2) } ", context: context)
        end
        assert context[:first_instrumenter_did_begin]
        assert context[:first_instrumenter_did_end]
        assert context[:second_instrumenter_did_begin]
        refute context[:second_instrumenter_did_end]
      end

      it "runs after_query even if a previous after_query raised an error" do
        context = {second_instrumenter_should_raise_end: true}
        err = assert_raises InstrumenterError do
          schema.execute(" { int(value: 2) } ", context: context)
        end
        # The error came from the second instrumenter:
        assert_equal :second_instrumenter_did_end, err.key
        # But the first instrumenter still got a chance to teardown
        assert context[:first_instrumenter_did_begin]
        assert context[:first_instrumenter_did_end]
        assert context[:second_instrumenter_did_begin]
        assert context[:second_instrumenter_did_end]
      end

      it "rescues execution errors from execute_query" do
        context = {raise_execution_error: true}
        res = schema.execute(" { int(value: 2) } ", context: context)
        assert_equal "Raised from trace execute_query", res["errors"].first["message"]
        refute res.key?("data"), "The query doesn't run"
      end

      it "can assign a query string there" do
        context = { extra_query_string: "{ __typename }"}
        res = schema.execute(nil, context: context)
        assert_equal "Query", res["data"]["__typename"]
      end
    end

    describe "within a multiplex" do
      let(:multiplex_schema) {
        Class.new(schema) {
          trace_with(LogInstrumenter.generate(:second_instrumenter))
          trace_with(LogInstrumenter.generate(:first_instrumenter))
        }
      }

      it "only runs after_multiplex if before_multiplex finished" do
        multiplex_ctx = {second_instrumenter_should_raise_begin: true}
        query_1_ctx = {}
        query_2_ctx = {}
        assert_raises InstrumenterError do
          multiplex_schema.multiplex(
            [
              {query: "{int(value: 1)}", context: query_1_ctx},
              {query: "{int(value: 2)}", context: query_2_ctx},
            ],
            context: multiplex_ctx
          )
        end

        assert multiplex_ctx[:first_instrumenter_did_begin]
        assert multiplex_ctx[:first_instrumenter_did_end]
        assert multiplex_ctx[:second_instrumenter_did_begin]
        refute multiplex_ctx[:second_instrumenter_did_end]
        # No query instrumentation was run at all
        assert_equal 0, query_1_ctx.size
        assert_equal 0, query_2_ctx.size
      end

      it "does full and partial query runs" do
        multiplex_ctx = {}
        query_1_ctx = {}
        query_2_ctx = {second_instrumenter_should_raise_begin: true}
        assert_raises InstrumenterError do
          multiplex_schema.multiplex(
            [
              { query: " { int(value: 2) } ", context: query_1_ctx },
              { query: " { int(value: 2) } ", context: query_2_ctx },
            ],
            context: multiplex_ctx
          )
        end

        # multiplex got a full run
        assert multiplex_ctx[:first_instrumenter_did_begin]
        assert multiplex_ctx[:first_instrumenter_did_end]
        assert multiplex_ctx[:second_instrumenter_did_begin]
        assert multiplex_ctx[:second_instrumenter_did_end]

        # query 1 got a full run
        assert query_1_ctx[:first_instrumenter_did_begin]
        assert query_1_ctx[:first_instrumenter_did_end]
        assert query_1_ctx[:second_instrumenter_did_begin]
        assert query_1_ctx[:second_instrumenter_did_end]

        # query 2 got a partial run
        assert query_2_ctx[:first_instrumenter_did_begin]
        assert query_2_ctx[:first_instrumenter_did_end]
        assert query_2_ctx[:second_instrumenter_did_begin]
        refute query_2_ctx[:second_instrumenter_did_end]
      end
    end
  end
end