artemis 0.9.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +17 -7
- data/Appraisals +15 -6
- data/CHANGELOG.md +32 -5
- data/Rakefile +1 -4
- data/artemis.gemspec +1 -2
- data/bin/console +3 -3
- data/gemfiles/graphql_2_0.gemfile +15 -0
- data/gemfiles/rails_60.gemfile +3 -3
- data/gemfiles/rails_71.gemfile +3 -3
- data/lib/artemis/railtie.rb +6 -8
- data/lib/artemis/version.rb +1 -1
- metadata +8 -37
- data/spec/adapters_spec.rb +0 -278
- data/spec/autoloading_spec.rb +0 -152
- data/spec/callbacks_spec.rb +0 -60
- data/spec/client_spec.rb +0 -228
- data/spec/endpoint_spec.rb +0 -49
- data/spec/fixtures/github/_repository_fields.graphql +0 -12
- data/spec/fixtures/github/repository.graphql +0 -6
- data/spec/fixtures/github/schema.json +0 -165225
- data/spec/fixtures/github/user.graphql +0 -6
- data/spec/fixtures/github/user_repositories.graphql +0 -13
- data/spec/fixtures/github.rb +0 -2
- data/spec/fixtures/responses/github/repository.yml +0 -17
- data/spec/fixtures/responses/github/user.json +0 -10
- data/spec/fixtures/responses/spotify_client/artist.yml +0 -5
- data/spec/spec_helper.rb +0 -49
- data/spec/test_helper_spec.rb +0 -94
data/spec/autoloading_spec.rb
DELETED
@@ -1,152 +0,0 @@
|
|
1
|
-
describe "#{GraphQL::Client} Autoloading" do
|
2
|
-
describe ".load_constant" do
|
3
|
-
it "loads the specified constant if there is a matching graphql file" do
|
4
|
-
Github.send(:remove_const, :User) if Github.constants.include?(:User)
|
5
|
-
|
6
|
-
Github.load_constant(:User)
|
7
|
-
|
8
|
-
expect(defined?(Github::User)).to eq('constant')
|
9
|
-
end
|
10
|
-
|
11
|
-
it "does nothing and returns nil if there is no matching file" do
|
12
|
-
expect(Github.load_constant(:DoesNotExist)).to be_nil
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
describe ".preload!" do
|
17
|
-
it "preloads all the graphQL files in the query paths" do
|
18
|
-
%i(User UserRepositories Repository RepositoryFields)
|
19
|
-
.select {|const_name| Github.constants.include?(const_name) }
|
20
|
-
.each {|const_name| Github.send(:remove_const, const_name) }
|
21
|
-
|
22
|
-
Github.preload!
|
23
|
-
|
24
|
-
expect(defined?(Github::User)).to eq('constant')
|
25
|
-
expect(defined?(Github::Repository)).to eq('constant')
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
it "dynamically loads the matching GraphQL query and sets it to a constant" do
|
30
|
-
Github.send(:remove_const, :User) if Github.constants.include?(:User)
|
31
|
-
|
32
|
-
query = Github::User
|
33
|
-
|
34
|
-
expect(query.document.to_query_string).to eq(<<~GRAPHQL.strip)
|
35
|
-
query Github__User {
|
36
|
-
user(login: "yuki24") {
|
37
|
-
id
|
38
|
-
name
|
39
|
-
}
|
40
|
-
}
|
41
|
-
GRAPHQL
|
42
|
-
end
|
43
|
-
|
44
|
-
it "dynamically loads the matching GraphQL fragment and sets it to a constant" do
|
45
|
-
Github.send(:remove_const, :RepositoryFields) if Github.constants.include?(:RepositoryFields)
|
46
|
-
|
47
|
-
query = Github::RepositoryFields
|
48
|
-
|
49
|
-
expect(query.document.to_query_string).to eq(<<~GRAPHQL.strip)
|
50
|
-
fragment Github__RepositoryFields on Repository {
|
51
|
-
name
|
52
|
-
nameWithOwner
|
53
|
-
url
|
54
|
-
updatedAt
|
55
|
-
languages(first: 1) {
|
56
|
-
nodes {
|
57
|
-
name
|
58
|
-
color
|
59
|
-
}
|
60
|
-
}
|
61
|
-
}
|
62
|
-
GRAPHQL
|
63
|
-
end
|
64
|
-
|
65
|
-
it "correctly loads the matching GraphQL query even when the top-level constant with the same name exists" do
|
66
|
-
# In Ruby <= 2.4 top-level constants can be looked up through a namespace, which turned out to be a bad practice.
|
67
|
-
# This has been removed in 2.5, but in earlier versions still suffer from this behaviour.
|
68
|
-
Github.send(:remove_const, :User) if Github.constants.include?(:User)
|
69
|
-
Object.send(:remove_const, :User) if Object.constants.include?(:User)
|
70
|
-
|
71
|
-
begin
|
72
|
-
Object.send(:const_set, :User, 1)
|
73
|
-
|
74
|
-
Github.user
|
75
|
-
ensure
|
76
|
-
Object.send(:remove_const, :User)
|
77
|
-
end
|
78
|
-
|
79
|
-
query = Github::User
|
80
|
-
|
81
|
-
expect(query.document.to_query_string).to eq(<<~GRAPHQL.strip)
|
82
|
-
query Github__User {
|
83
|
-
user(login: "yuki24") {
|
84
|
-
id
|
85
|
-
name
|
86
|
-
}
|
87
|
-
}
|
88
|
-
GRAPHQL
|
89
|
-
end
|
90
|
-
|
91
|
-
it "raises an exception when the path was resolved but the file does not exist" do
|
92
|
-
begin
|
93
|
-
Github.graphql_file_paths << "github/removed.graphql"
|
94
|
-
|
95
|
-
expect { Github::Removed }.to raise_error(Errno::ENOENT)
|
96
|
-
ensure
|
97
|
-
Github.graphql_file_paths.delete("github/removed.graphql")
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
it "raises an NameError when there is no graphql file that matches the const name" do
|
102
|
-
expect { Github::DoesNotExist }.to raise_error(NameError)
|
103
|
-
end
|
104
|
-
|
105
|
-
xit "defines the query method when the matching class method gets called for the first time" do
|
106
|
-
Github.undef_method(:user) if Github.public_instance_methods.include?(:user)
|
107
|
-
|
108
|
-
Github.user
|
109
|
-
|
110
|
-
expect(Github.public_instance_methods).to include(:user)
|
111
|
-
end
|
112
|
-
|
113
|
-
it "raises an NameError when there is no graphql file that matches the class method name" do
|
114
|
-
expect { Github.does_not_exist }.to raise_error(NameError)
|
115
|
-
end
|
116
|
-
|
117
|
-
it "raises an NameError when the class method name matches a fragment name" do
|
118
|
-
expect { Github.repository_fields_fragment }.to raise_error(NameError)
|
119
|
-
end
|
120
|
-
|
121
|
-
it "responds to a class method that has a matching graphQL file" do
|
122
|
-
expect(Github).to respond_to(:user)
|
123
|
-
end
|
124
|
-
|
125
|
-
it "does not respond to class methods that do not have a matching graphQL file" do
|
126
|
-
expect(Github).not_to respond_to(:does_not_exist)
|
127
|
-
end
|
128
|
-
|
129
|
-
xit "defines the query method when the matching instance method gets called for the first time" do
|
130
|
-
Github.undef_method(:user) if Github.public_instance_methods.include?(:user)
|
131
|
-
|
132
|
-
Github.new.user
|
133
|
-
|
134
|
-
expect(Github.public_instance_methods).to include(:user)
|
135
|
-
end
|
136
|
-
|
137
|
-
it "raises an NameError when there is no graphql file that matches the instance method name" do
|
138
|
-
expect { Github.new.does_not_exist }.to raise_error(NameError)
|
139
|
-
end
|
140
|
-
|
141
|
-
it "raises an NameError when the instance method name matches a fragment name" do
|
142
|
-
expect { Github.new.repository_fields_fragment }.to raise_error(NameError)
|
143
|
-
end
|
144
|
-
|
145
|
-
it "responds to the method that has a matching graphQL file" do
|
146
|
-
expect(Github.new).to respond_to(:user)
|
147
|
-
end
|
148
|
-
|
149
|
-
it "does not respond to methods that do not have a matching graphQL file" do
|
150
|
-
expect(Github.new).not_to respond_to(:does_not_exist)
|
151
|
-
end
|
152
|
-
end
|
data/spec/callbacks_spec.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
-
|
3
|
-
describe "#{GraphQL::Client} Callbacks" do
|
4
|
-
Client = Class.new(Artemis::Client) do
|
5
|
-
def self.name
|
6
|
-
'Github'
|
7
|
-
end
|
8
|
-
|
9
|
-
mattr_accessor :before_callback, :after_callback
|
10
|
-
self.before_callback = nil
|
11
|
-
self.after_callback = nil
|
12
|
-
|
13
|
-
before_execute do |document, operation_name, variables, context|
|
14
|
-
self.before_callback = document, operation_name, variables, context
|
15
|
-
end
|
16
|
-
|
17
|
-
after_execute do |data, errors, extensions|
|
18
|
-
self.after_callback = data, errors, extensions
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
Spotify = Class.new(Artemis::Client) do
|
23
|
-
def self.name
|
24
|
-
'Spotify'
|
25
|
-
end
|
26
|
-
|
27
|
-
before_execute do
|
28
|
-
raise "this callback should not get invoked"
|
29
|
-
end
|
30
|
-
|
31
|
-
after_execute do
|
32
|
-
raise "this callback should not get invoked"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
describe ".before_execute" do
|
37
|
-
it "gets invoked before executing" do
|
38
|
-
Client.repository(owner: "yuki24", name: "artemis", context: { user_id: 'yuki24' })
|
39
|
-
|
40
|
-
document, operation_name, variables, context = Client.before_callback
|
41
|
-
|
42
|
-
expect(document).to eq(Client::Repository.document)
|
43
|
-
expect(operation_name).to eq('Client__Repository')
|
44
|
-
expect(variables).to eq("name" => "artemis", "owner" => "yuki24")
|
45
|
-
expect(context).to eq(user_id: 'yuki24')
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
describe ".after_execute" do
|
50
|
-
it "gets invoked after executing" do
|
51
|
-
Client.user
|
52
|
-
|
53
|
-
data, errors, extensions = Client.after_callback
|
54
|
-
|
55
|
-
expect(data).to eq("test" => "data")
|
56
|
-
expect(errors).to eq([])
|
57
|
-
expect(extensions).to eq({})
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
data/spec/client_spec.rb
DELETED
@@ -1,228 +0,0 @@
|
|
1
|
-
describe GraphQL::Client do
|
2
|
-
before do
|
3
|
-
requests.clear
|
4
|
-
end
|
5
|
-
|
6
|
-
describe ".lookup_graphql_file" do
|
7
|
-
it "returns the path to the matching graph file" do
|
8
|
-
expect(Github.resolve_graphql_file_path("user")).to eq("#{PROJECT_DIR}/spec/fixtures/github/user.graphql")
|
9
|
-
end
|
10
|
-
|
11
|
-
it "returns nil if the file is missing" do
|
12
|
-
expect(Github.resolve_graphql_file_path("does_not_exist")).to be_nil
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
describe ".graphql_file_paths" do
|
17
|
-
it "returns a list of GraphQL files (*.graphql) in the query_paths" do
|
18
|
-
Github.instance_variable_set :@graphql_file_paths, nil
|
19
|
-
original = Github.query_paths
|
20
|
-
|
21
|
-
Github.query_paths = [File.join(PROJECT_DIR, 'tmp')]
|
22
|
-
|
23
|
-
begin
|
24
|
-
FileUtils.mkdir "./tmp/github" if !Dir.exist?("./tmp/github")
|
25
|
-
|
26
|
-
with_files "./tmp/github/text.txt", "./tmp/github/sale.graphql" do
|
27
|
-
expect(Github.graphql_file_paths).to eq(["#{PROJECT_DIR}/tmp/github/sale.graphql"])
|
28
|
-
end
|
29
|
-
ensure
|
30
|
-
Github.instance_variable_set :@graphql_file_paths, nil
|
31
|
-
Github.query_paths = original
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
it "can make a GraphQL request without variables" do
|
37
|
-
Github.user
|
38
|
-
|
39
|
-
request = requests[0]
|
40
|
-
|
41
|
-
expect(request.operation_name).to eq('Github__User')
|
42
|
-
expect(request.variables).to be_empty
|
43
|
-
expect(request.context).to eq({})
|
44
|
-
expect(request.document.to_query_string).to eq(<<~GRAPHQL.strip)
|
45
|
-
query Github__User {
|
46
|
-
user(login: "yuki24") {
|
47
|
-
id
|
48
|
-
name
|
49
|
-
}
|
50
|
-
}
|
51
|
-
GRAPHQL
|
52
|
-
end
|
53
|
-
|
54
|
-
it "can make a GraphQL request with variables" do
|
55
|
-
Github.repository(owner: "yuki24", name: "artemis")
|
56
|
-
|
57
|
-
request = requests[0]
|
58
|
-
|
59
|
-
expect(request.operation_name).to eq('Github__Repository')
|
60
|
-
expect(request.variables).to eq("owner" => "yuki24", "name" => "artemis")
|
61
|
-
expect(request.context).to eq({})
|
62
|
-
expect(request.document.to_query_string).to eq(<<~GRAPHQL.strip)
|
63
|
-
query Github__Repository($owner: String!, $name: String!) {
|
64
|
-
repository(owner: $owner, name: $name) {
|
65
|
-
name
|
66
|
-
nameWithOwner
|
67
|
-
}
|
68
|
-
}
|
69
|
-
GRAPHQL
|
70
|
-
end
|
71
|
-
|
72
|
-
it "can make a GraphQL request with a query that contains fragments" do
|
73
|
-
Github.user_repositories(login: "yuki24", size: 10)
|
74
|
-
|
75
|
-
request = requests[0]
|
76
|
-
|
77
|
-
expect(request.operation_name).to eq('Github__UserRepositories')
|
78
|
-
expect(request.variables).to eq('login' => 'yuki24', 'size' => 10)
|
79
|
-
expect(request.context).to eq({})
|
80
|
-
expect(request.document.to_query_string).to eq(<<~GRAPHQL.strip)
|
81
|
-
query Github__UserRepositories($login: String!, $size: Int!) {
|
82
|
-
user(login: $login) {
|
83
|
-
id
|
84
|
-
name
|
85
|
-
repositories(first: $size) {
|
86
|
-
nodes {
|
87
|
-
name
|
88
|
-
description
|
89
|
-
...Github__RepositoryFields
|
90
|
-
}
|
91
|
-
}
|
92
|
-
}
|
93
|
-
}
|
94
|
-
|
95
|
-
fragment Github__RepositoryFields on Repository {
|
96
|
-
name
|
97
|
-
nameWithOwner
|
98
|
-
url
|
99
|
-
updatedAt
|
100
|
-
languages(first: 1) {
|
101
|
-
nodes {
|
102
|
-
name
|
103
|
-
color
|
104
|
-
}
|
105
|
-
}
|
106
|
-
}
|
107
|
-
GRAPHQL
|
108
|
-
end
|
109
|
-
|
110
|
-
it "can make a GraphQL request with #execute" do
|
111
|
-
Github.execute(:repository, owner: "yuki24", name: "artemis")
|
112
|
-
|
113
|
-
request = requests[0]
|
114
|
-
|
115
|
-
expect(request.operation_name).to eq('Github__Repository')
|
116
|
-
expect(request.variables).to eq("owner" => "yuki24", "name" => "artemis")
|
117
|
-
expect(request.context).to eq({})
|
118
|
-
expect(request.document.to_query_string).to eq(<<~GRAPHQL.strip)
|
119
|
-
query Github__Repository($owner: String!, $name: String!) {
|
120
|
-
repository(owner: $owner, name: $name) {
|
121
|
-
name
|
122
|
-
nameWithOwner
|
123
|
-
}
|
124
|
-
}
|
125
|
-
GRAPHQL
|
126
|
-
end
|
127
|
-
|
128
|
-
it "raises an error when the specified graphql file does not exist" do
|
129
|
-
expect { Github.execute(:does_not_exist) }
|
130
|
-
.to raise_error(Artemis::GraphQLFileNotFound)
|
131
|
-
.with_message(/Query does_not_exist\.graphql not found/)
|
132
|
-
end
|
133
|
-
|
134
|
-
it "assigns context to the request when provided as an argument" do
|
135
|
-
context = { headers: { Authorization: 'bearer ...' } }
|
136
|
-
|
137
|
-
Github.repository(owner: "yuki24", name: "artemis", context: context)
|
138
|
-
|
139
|
-
expect(requests[0].context).to eq(context)
|
140
|
-
end
|
141
|
-
|
142
|
-
it "can create a client that always assigns the provided context to the request" do
|
143
|
-
context = { headers: { Authorization: 'bearer ...' } }
|
144
|
-
client = Github.with_context(context)
|
145
|
-
|
146
|
-
client.repository(owner: "yuki24", name: "artemis")
|
147
|
-
client.repository(owner: "yuki24", name: "artemis")
|
148
|
-
|
149
|
-
expect(requests[0].context).to eq(context)
|
150
|
-
expect(requests[1].context).to eq(context)
|
151
|
-
end
|
152
|
-
|
153
|
-
it "assigns the default context to a GraphQL request if present" do
|
154
|
-
begin
|
155
|
-
Github.default_context = { headers: { Authorization: 'bearer ...' } }
|
156
|
-
Github.repository(owner: "yuki24", name: "artemis")
|
157
|
-
|
158
|
-
expect(requests[0].context).to eq(headers: { Authorization: 'bearer ...' })
|
159
|
-
ensure
|
160
|
-
Github.default_context = { }
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
it "can make a GraphQL request with all of .default_context, with_context(...) and the :context argument" do
|
165
|
-
begin
|
166
|
-
Github.default_context = { headers: { 'User-Agent': 'Artemis', 'X-key': 'value', Authorization: 'token ...' } }
|
167
|
-
Github
|
168
|
-
.with_context({ headers: { 'X-key': 'overridden' } })
|
169
|
-
.repository(owner: "yuki24", name: "artemis", context: { headers: { Authorization: 'bearer ...' } })
|
170
|
-
|
171
|
-
expect(requests[0].context).to eq(
|
172
|
-
headers: {
|
173
|
-
'User-Agent': 'Artemis',
|
174
|
-
'X-key': 'overridden',
|
175
|
-
Authorization: 'bearer ...',
|
176
|
-
}
|
177
|
-
)
|
178
|
-
ensure
|
179
|
-
Github.default_context = { }
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
it "can batch multiple requests using Multiplex" do
|
184
|
-
Github.multiplex do |queue|
|
185
|
-
queue.repository(owner: "yuki24", name: "artemis", context: { headers: { Authorization: 'bearer ...' } })
|
186
|
-
queue.user
|
187
|
-
end
|
188
|
-
|
189
|
-
repository_query, user_query = requests[0].queries
|
190
|
-
|
191
|
-
expect(repository_query[:operationName]).to eq('Github__Repository')
|
192
|
-
expect(repository_query[:variables]).to eq("owner" => "yuki24", "name" => "artemis")
|
193
|
-
expect(repository_query[:context]).to eq({ headers: { Authorization: 'bearer ...' } })
|
194
|
-
expect(repository_query[:query]).to eq(<<~GRAPHQL.strip)
|
195
|
-
query Github__Repository($owner: String!, $name: String!) {
|
196
|
-
repository(owner: $owner, name: $name) {
|
197
|
-
name
|
198
|
-
nameWithOwner
|
199
|
-
}
|
200
|
-
}
|
201
|
-
GRAPHQL
|
202
|
-
|
203
|
-
expect(user_query[:operationName]).to eq('Github__User')
|
204
|
-
expect(user_query[:variables]).to be_empty
|
205
|
-
expect(user_query[:context]).to eq({})
|
206
|
-
expect(user_query[:query]).to eq(<<~GRAPHQL.strip)
|
207
|
-
query Github__User {
|
208
|
-
user(login: "yuki24") {
|
209
|
-
id
|
210
|
-
name
|
211
|
-
}
|
212
|
-
}
|
213
|
-
GRAPHQL
|
214
|
-
end
|
215
|
-
|
216
|
-
private
|
217
|
-
|
218
|
-
def requests
|
219
|
-
Artemis::Adapters::TestAdapter.requests
|
220
|
-
end
|
221
|
-
|
222
|
-
def with_files(*files)
|
223
|
-
files.each {|file| FileUtils.touch(file) }
|
224
|
-
yield
|
225
|
-
ensure
|
226
|
-
files.each {|file| File.delete(file) }
|
227
|
-
end
|
228
|
-
end
|
data/spec/endpoint_spec.rb
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
describe Artemis::GraphQLEndpoint do
|
2
|
-
after do
|
3
|
-
Artemis::GraphQLEndpoint.const_get(:ENDPOINT_INSTANCES).delete("gitlab")
|
4
|
-
end
|
5
|
-
|
6
|
-
describe ".lookup" do
|
7
|
-
it "raises an exception when the service is missing" do
|
8
|
-
expect { Artemis::GraphQLEndpoint.lookup(:does_not_exit) }.to raise_error(Artemis::EndpointNotFound)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
it "can register an endpoint" do
|
13
|
-
endpoint = Artemis::GraphQLEndpoint.register!(:gitlab, url: "https://api.gitlab.com/graphql")
|
14
|
-
|
15
|
-
expect(endpoint.url).to eq("https://api.gitlab.com/graphql")
|
16
|
-
expect(endpoint.connection).to be_instance_of(Artemis::Adapters::NetHttpAdapter)
|
17
|
-
end
|
18
|
-
|
19
|
-
it "can look up a registered endpoint" do
|
20
|
-
Artemis::GraphQLEndpoint.register!(:gitlab, url: "https://api.gitlab.com/graphql")
|
21
|
-
|
22
|
-
endpoint = Artemis::GraphQLEndpoint.lookup(:gitlab)
|
23
|
-
|
24
|
-
expect(endpoint.url).to eq("https://api.gitlab.com/graphql")
|
25
|
-
expect(endpoint.connection).to be_instance_of(Artemis::Adapters::NetHttpAdapter) # Not a fan of this test but for now
|
26
|
-
|
27
|
-
# FIXME: This #schema method makes a network call.
|
28
|
-
# expect(endpoint.schema).to eq(...)
|
29
|
-
end
|
30
|
-
|
31
|
-
it "can register an endpoint with options" do
|
32
|
-
options = {
|
33
|
-
adapter: :test,
|
34
|
-
timeout: 10,
|
35
|
-
# schema_path: nil,
|
36
|
-
pool_size: 25,
|
37
|
-
}
|
38
|
-
|
39
|
-
endpoint = Artemis::GraphQLEndpoint.register!(:gitlab, url: "https://api.gitlab.com/graphql", **options)
|
40
|
-
|
41
|
-
expect(endpoint.url).to eq("https://api.gitlab.com/graphql")
|
42
|
-
expect(endpoint.timeout).to eq(10)
|
43
|
-
expect(endpoint.pool_size).to eq(25)
|
44
|
-
expect(endpoint.connection).to be_instance_of(Artemis::Adapters::TestAdapter) # Not a fan of this test but for now
|
45
|
-
|
46
|
-
# FIXME: needs an example schema (and specify the :schema_path option) to test this.
|
47
|
-
# expect(endpoint.schema).to eq(...)
|
48
|
-
end
|
49
|
-
end
|