stub_requests 0.1.2 → 0.1.3
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/.simplecov +1 -0
- data/CHANGELOG.md +8 -0
- data/README.md +27 -0
- data/gemfiles/webmock_2.3.gemfile.lock +1 -1
- data/gemfiles/webmock_3.5.gemfile.lock +1 -1
- data/gemfiles/webmock_develop.gemfile.lock +1 -1
- data/lib/stub_requests.rb +19 -14
- data/lib/stub_requests/api.rb +30 -16
- data/lib/stub_requests/argument_validation.rb +12 -12
- data/lib/stub_requests/endpoint.rb +12 -10
- data/lib/stub_requests/exceptions.rb +2 -6
- data/lib/stub_requests/metrics.rb +5 -4
- data/lib/stub_requests/metrics/{endpoint_stat.rb → endpoint.rb} +23 -22
- data/lib/stub_requests/metrics/registry.rb +25 -25
- data/lib/stub_requests/metrics/{stub_stat.rb → request.rb} +22 -13
- data/lib/stub_requests/observable.rb +62 -0
- data/lib/stub_requests/observable/registry.rb +152 -0
- data/lib/stub_requests/observable/subscription.rb +58 -0
- data/lib/stub_requests/property.rb +9 -3
- data/lib/stub_requests/property/validator.rb +4 -4
- data/lib/stub_requests/registration.rb +87 -0
- data/lib/stub_requests/registration/endpoint.rb +107 -0
- data/lib/stub_requests/registration/endpoints.rb +156 -0
- data/lib/stub_requests/registration/registry.rb +112 -0
- data/lib/stub_requests/registration/service.rb +85 -0
- data/lib/stub_requests/uri.rb +1 -1
- data/lib/stub_requests/version.rb +1 -1
- data/lib/tasks/changelog.rake +11 -2
- data/update_docs.sh +33 -0
- metadata +13 -8
- data/bin/update_docs.sh +0 -16
- data/lib/stub_requests/endpoint_registry.rb +0 -159
- data/lib/stub_requests/service.rb +0 -77
- data/lib/stub_requests/service_registry.rb +0 -104
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be0196cbd37c83eff04abe21413a0bded52b335db5437e6c31eaf79b7484d81b
|
4
|
+
data.tar.gz: 2c4d16e0b0d23d285a7b2532cbb6d3f3507348c05f473bacdfe52dee352ff1e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd5f10556ecfb6aef179cf59dcfaf5938eadbda4c7ae64f0556ffd2502caf434e0747e1f3b8e7ed73a7e99a2636f23fb825967c5dea919ccc486ae8156da2973
|
7
|
+
data.tar.gz: 78e59cb1fbe7ad99282f0162ab0cdb7cbe97a77647dd5be0055edc6e106ac132fd48cd6fe92a764551719e541c114949081b1ed8c6e2b91a92be990f75b10358
|
data/.simplecov
CHANGED
@@ -24,6 +24,7 @@ SimpleCov.start do
|
|
24
24
|
add_filter "/gemfiles/"
|
25
25
|
add_filter "/lib/stub_requests/core_ext/array/extract_options.rb"
|
26
26
|
add_filter "/lib/stub_requests/core_ext/class/attribute.rb"
|
27
|
+
add_filter "/lib/stub_requests/core_ext/object/blank.rb"
|
27
28
|
add_filter "/lib/stub_requests/core_ext/kernel/singleton_class.rb"
|
28
29
|
add_filter "/lib/stub_requests/core_ext/module/redefine_method.rb"
|
29
30
|
end
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,14 @@
|
|
4
4
|
|
5
5
|
[Full Changelog](https://github.com/mhenrixon/stub_requests/compare/v0.1.0...HEAD)
|
6
6
|
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Script updating documentation [\#9](https://github.com/mhenrixon/stub_requests/pull/9) ([mhenrixon](https://github.com/mhenrixon))
|
10
|
+
- Add a simple script to update documentation [\#8](https://github.com/mhenrixon/stub_requests/pull/8) ([mhenrixon](https://github.com/mhenrixon))
|
11
|
+
- Improve documentation [\#7](https://github.com/mhenrixon/stub_requests/pull/7) ([mhenrixon](https://github.com/mhenrixon))
|
12
|
+
- Initial metrics implementation [\#6](https://github.com/mhenrixon/stub_requests/pull/6) ([mhenrixon](https://github.com/mhenrixon))
|
13
|
+
- Remove the docs folder in preference of a branch [\#5](https://github.com/mhenrixon/stub_requests/pull/5) ([mhenrixon](https://github.com/mhenrixon))
|
14
|
+
|
7
15
|
**Merged pull requests:**
|
8
16
|
|
9
17
|
- Allow older webmock versions [\#4](https://github.com/mhenrixon/stub_requests/pull/4) ([mhenrixon](https://github.com/mhenrixon))
|
data/README.md
CHANGED
@@ -14,11 +14,13 @@ This is achieve by keeping a registry over the service endpoints.
|
|
14
14
|
|
15
15
|
<!-- MarkdownTOC -->
|
16
16
|
|
17
|
+
- [Required ruby version](#required-ruby-version)
|
17
18
|
- [Installation](#installation)
|
18
19
|
- [Usage](#usage)
|
19
20
|
- [Register service endpoints](#register-service-endpoints)
|
20
21
|
- [Stubbing service endpoints](#stubbing-service-endpoints)
|
21
22
|
- [Metrics](#metrics)
|
23
|
+
- [Observing endpoint invocations](#observing-endpoint-invocations)
|
22
24
|
- [Future Improvements](#future-improvements)
|
23
25
|
- [API Client Gem](#api-client-gem)
|
24
26
|
- [Development](#development)
|
@@ -28,6 +30,11 @@ This is achieve by keeping a registry over the service endpoints.
|
|
28
30
|
|
29
31
|
<!-- /MarkdownTOC -->
|
30
32
|
|
33
|
+
<a id="required-ruby-version"></a>
|
34
|
+
## Required ruby version
|
35
|
+
|
36
|
+
Ruby version >= 2.3
|
37
|
+
|
31
38
|
<a id="installation"></a>
|
32
39
|
## Installation
|
33
40
|
|
@@ -112,6 +119,26 @@ StubRequests.configure do |config|
|
|
112
119
|
end
|
113
120
|
```
|
114
121
|
|
122
|
+
<a id="observing-endpoint-invocations"></a>
|
123
|
+
### Observing endpoint invocations
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
# To jump into pry when a request is called
|
127
|
+
callback = lambda do |request|
|
128
|
+
p request
|
129
|
+
binding.pry
|
130
|
+
end
|
131
|
+
|
132
|
+
callback = ->(request) { p request; binding.pry }
|
133
|
+
|
134
|
+
StubRequests.subscribe_to(:document_service, :show, :get, callback)
|
135
|
+
```
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
# To unsubscribe from notifications
|
139
|
+
StubRequests.unsubscribe_from(:document_service, :show, :get)
|
140
|
+
```
|
141
|
+
|
115
142
|
<a id="future-improvements"></a>
|
116
143
|
## Future Improvements
|
117
144
|
|
data/lib/stub_requests.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "forwardable"
|
4
|
+
require "singleton"
|
5
|
+
|
3
6
|
require "concurrent/array"
|
4
7
|
require "concurrent/map"
|
5
8
|
require "docile"
|
6
|
-
require "singleton"
|
7
9
|
require "webmock"
|
8
10
|
require "webmock/api"
|
9
11
|
require "webmock/stub_registry"
|
@@ -11,30 +13,33 @@ require "webmock/request_stub"
|
|
11
13
|
|
12
14
|
require "stub_requests/version"
|
13
15
|
|
16
|
+
require "stub_requests/argument_validation"
|
14
17
|
require "stub_requests/core_ext"
|
15
18
|
require "stub_requests/exceptions"
|
16
19
|
require "stub_requests/hash_util"
|
17
|
-
require "stub_requests/argument_validation"
|
18
20
|
require "stub_requests/property"
|
19
21
|
require "stub_requests/property/validator"
|
22
|
+
require "stub_requests/uri"
|
23
|
+
require "stub_requests/uri/scheme"
|
24
|
+
require "stub_requests/uri/suffix"
|
25
|
+
require "stub_requests/uri/validator"
|
26
|
+
require "stub_requests/uri/builder"
|
20
27
|
require "stub_requests/configuration"
|
21
28
|
|
22
|
-
require "stub_requests/
|
23
|
-
require "stub_requests/
|
29
|
+
require "stub_requests/observable"
|
30
|
+
require "stub_requests/observable/subscription"
|
31
|
+
require "stub_requests/observable/registry"
|
24
32
|
|
25
33
|
require "stub_requests/metrics"
|
26
|
-
require "stub_requests/metrics/
|
34
|
+
require "stub_requests/metrics/endpoint"
|
35
|
+
require "stub_requests/metrics/request"
|
27
36
|
require "stub_requests/metrics/registry"
|
28
|
-
require "stub_requests/metrics/stub_stat"
|
29
37
|
|
30
|
-
require "stub_requests/
|
31
|
-
require "stub_requests/
|
32
|
-
|
33
|
-
require "stub_requests/
|
34
|
-
require "stub_requests/
|
35
|
-
require "stub_requests/uri/suffix"
|
36
|
-
require "stub_requests/uri/validator"
|
37
|
-
require "stub_requests/uri/builder"
|
38
|
+
require "stub_requests/registration"
|
39
|
+
require "stub_requests/registration/endpoint"
|
40
|
+
require "stub_requests/registration/endpoints"
|
41
|
+
require "stub_requests/registration/service"
|
42
|
+
require "stub_requests/registration/registry"
|
38
43
|
|
39
44
|
require "stub_requests/webmock/builder"
|
40
45
|
require "stub_requests/webmock/stub_registry_extension"
|
data/lib/stub_requests/api.rb
CHANGED
@@ -21,15 +21,6 @@ module StubRequests
|
|
21
21
|
# @!parse extend self
|
22
22
|
extend self
|
23
23
|
|
24
|
-
# :reek:LongParameterList { max_params: 4 }
|
25
|
-
# @api private
|
26
|
-
def self._stub_endpoint(service_id, endpoint_id, uri_replacements = {}, options = {})
|
27
|
-
_service, endpoint, uri = StubRequests::URI.for_service_endpoint(service_id, endpoint_id, uri_replacements)
|
28
|
-
endpoint_stub = WebMock::Builder.build(endpoint.verb, uri, options)
|
29
|
-
|
30
|
-
::WebMock::StubRegistry.instance.register_request_stub(endpoint_stub)
|
31
|
-
end
|
32
|
-
|
33
24
|
# :nodoc:
|
34
25
|
def self.included(base)
|
35
26
|
base.send(:extend, self)
|
@@ -56,9 +47,7 @@ module StubRequests
|
|
56
47
|
#
|
57
48
|
# :reek:UtilityFunction
|
58
49
|
def register_service(service_id, service_uri, &block)
|
59
|
-
|
60
|
-
Docile.dsl_eval(service.endpoints, &block) if block.present?
|
61
|
-
service
|
50
|
+
StubRequests::Registration.register_service(service_id, service_uri, &block)
|
62
51
|
end
|
63
52
|
|
64
53
|
#
|
@@ -97,11 +86,36 @@ module StubRequests
|
|
97
86
|
# :reek:UtilityFunction
|
98
87
|
# :reek:LongParameterList { max_params: 5 }
|
99
88
|
def stub_endpoint(service_id, endpoint_id, uri_replacements = {}, options = {}, &callback)
|
100
|
-
|
101
|
-
|
89
|
+
StubRequests::Registration.stub_endpoint(service_id, endpoint_id, uri_replacements, options, &callback)
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# Subscribe to notifications for a service endpoint
|
94
|
+
#
|
95
|
+
# @param [Symbol] service_id the id of a service
|
96
|
+
# @param [Symbol] endpoint_id the id of an endpoint
|
97
|
+
# @param [Symbol] verb an HTTP verb/method
|
98
|
+
# @param [Proc] callback a Proc to call when receiving response
|
99
|
+
#
|
100
|
+
# @return [void]
|
101
|
+
#
|
102
|
+
# :reek:UtilityFunction
|
103
|
+
# :reek:LongParameterList
|
104
|
+
def subscribe_to(service_id, endpoint_id, verb, callback)
|
105
|
+
StubRequests::Observable.subscribe_to(service_id, endpoint_id, verb, callback)
|
106
|
+
end
|
102
107
|
|
103
|
-
|
104
|
-
|
108
|
+
#
|
109
|
+
# Unsubscribe from notifications for a service endpoint
|
110
|
+
#
|
111
|
+
# @param [Symbol] service_id the id of a service
|
112
|
+
# @param [Symbol] endpoint_id the id of an endpoint
|
113
|
+
#
|
114
|
+
# @return [void]
|
115
|
+
#
|
116
|
+
# :reek:UtilityFunction
|
117
|
+
def unsubscribe_from(service_id, endpoint_id, verb)
|
118
|
+
StubRequests::Observable.unsubscribe_from(service_id, endpoint_id, verb)
|
105
119
|
end
|
106
120
|
end
|
107
121
|
end
|
@@ -22,29 +22,29 @@ module StubRequests
|
|
22
22
|
# Require the value to be any of the types past in
|
23
23
|
#
|
24
24
|
#
|
25
|
-
# @param [
|
26
|
-
# @param [
|
25
|
+
# @param [Symbol] name the name of the argument
|
26
|
+
# @param [Object] value the actual value of the argument
|
27
|
+
# @param [Array, Class, Module] type nil the expected argument value class
|
27
28
|
#
|
28
29
|
# @raise [InvalidArgumentType] when the value is disallowed
|
29
30
|
#
|
30
|
-
# @return [
|
31
|
+
# @return [void]
|
31
32
|
#
|
32
33
|
# :reek:UtilityFunction
|
33
|
-
def validate!(name
|
34
|
-
|
34
|
+
def validate!(name:, value:, type:)
|
35
|
+
validate_type!(:name, name, [Symbol, String]) unless name
|
36
|
+
validate_type!(name, value, type) if type
|
37
|
+
end
|
35
38
|
|
36
|
-
|
37
|
-
|
39
|
+
# :reek:UtilityFunction
|
40
|
+
def validate_type!(name, value, type)
|
41
|
+
expected_types = Array(type).flatten
|
42
|
+
return if expected_types.any? { |is_a| value.is_a?(is_a) }
|
38
43
|
|
39
44
|
raise StubRequests::InvalidArgumentType,
|
40
45
|
name: name,
|
41
46
|
actual: value.class,
|
42
47
|
expected: expected_types
|
43
48
|
end
|
44
|
-
|
45
|
-
# :reek:UtilityFunction
|
46
|
-
def validate(value, expected_types)
|
47
|
-
expected_types.any? { |type| value.is_a?(type) }
|
48
|
-
end
|
49
49
|
end
|
50
50
|
end
|
@@ -13,24 +13,25 @@ module StubRequests
|
|
13
13
|
# @author Mikael Henriksson <mikael@zoolutions.se>
|
14
14
|
#
|
15
15
|
class Endpoint
|
16
|
+
extend Forwardable
|
17
|
+
|
16
18
|
include Comparable
|
17
19
|
include Property
|
18
20
|
|
21
|
+
# Delegate id, uri and endpoints to service
|
22
|
+
delegate [:id, :uri, :endpoints] => :service
|
19
23
|
#
|
20
24
|
# @!attribute [rw] id
|
21
25
|
# @return [Symbol] the id of the endpoint
|
22
26
|
property :id, type: Symbol
|
23
|
-
|
24
27
|
#
|
25
28
|
# @!attribute [rw] verb
|
26
29
|
# @return [Symbol] a HTTP verb
|
27
30
|
property :verb, type: Symbol
|
28
|
-
|
29
31
|
#
|
30
32
|
# @!attribute [rw] uri_template
|
31
33
|
# @return [String] a string template for the endpoint
|
32
34
|
property :uri_template, type: String
|
33
|
-
|
34
35
|
#
|
35
36
|
# @!attribute [rw] options
|
36
37
|
# @see
|
@@ -38,7 +39,7 @@ module StubRequests
|
|
38
39
|
property :options, type: Hash, default: {}
|
39
40
|
|
40
41
|
#
|
41
|
-
# An endpoint for a specific {Service}
|
42
|
+
# An endpoint for a specific {StubRequests::Registration::Service}
|
42
43
|
#
|
43
44
|
# @param [Symbol] endpoint_id a descriptive id for the endpoint
|
44
45
|
# @param [Symbol] verb a HTTP verb
|
@@ -49,11 +50,12 @@ module StubRequests
|
|
49
50
|
# @option options [optional, Array, Exception, StandardError, String] :error for request_stub.to_raise
|
50
51
|
# @option options [optional, TrueClass] :timeout for request_stub.to_timeout
|
51
52
|
#
|
52
|
-
def initialize(endpoint_id, verb, uri_template, options = {})
|
53
|
-
self.
|
54
|
-
self.
|
55
|
-
self.
|
56
|
-
self.
|
53
|
+
def initialize(service, endpoint_id, verb, uri_template, options = {})
|
54
|
+
self.service = service
|
55
|
+
self.id = endpoint_id
|
56
|
+
self.verb = verb
|
57
|
+
self.uri_template = uri_template
|
58
|
+
self.options = options
|
57
59
|
end
|
58
60
|
|
59
61
|
#
|
@@ -67,7 +69,7 @@ module StubRequests
|
|
67
69
|
# @option options [optional, Array, Exception, StandardError, String] :error for request_stub.to_raise
|
68
70
|
# @option options [optional, TrueClass] :timeout for request_stub.to_timeout
|
69
71
|
#
|
70
|
-
# @return [Endpoint] returns the updated endpoint
|
72
|
+
# @return [Registration::Endpoint] returns the updated endpoint
|
71
73
|
#
|
72
74
|
def update(verb, uri_template, options)
|
73
75
|
self.verb = verb
|
@@ -18,13 +18,9 @@ module StubRequests
|
|
18
18
|
class EndpointNotFound < Error; end
|
19
19
|
|
20
20
|
#
|
21
|
-
#
|
21
|
+
# Class InvalidCallback is raised when a callback argument doesn't have the correct number of arguments
|
22
22
|
#
|
23
|
-
class
|
24
|
-
def initialize(actual, expected)
|
25
|
-
super("Expected `#{actual}` to be any of [#{expected}]")
|
26
|
-
end
|
27
|
-
end
|
23
|
+
class InvalidCallback < Error; end
|
28
24
|
|
29
25
|
#
|
30
26
|
# InvalidArgumentType is raised when an argument is not of the expected type
|
@@ -8,7 +8,7 @@
|
|
8
8
|
#
|
9
9
|
module StubRequests
|
10
10
|
#
|
11
|
-
# Module Metrics contains logic for collecting metrics about {
|
11
|
+
# Module Metrics contains logic for collecting metrics about {Metrics::Endpoint} and {Metrics::Request}
|
12
12
|
#
|
13
13
|
# @author Mikael Henriksson <mikael@zoolutions.se>
|
14
14
|
# @since 0.1.2
|
@@ -17,11 +17,12 @@ module StubRequests
|
|
17
17
|
#
|
18
18
|
# Records metrics about stubbed endpoints
|
19
19
|
#
|
20
|
-
#
|
21
|
-
# @param [
|
20
|
+
#
|
21
|
+
# @param [Registration::Service] service a Service
|
22
|
+
# @param [Registration::Endpoint] endpoint an Endpoint
|
22
23
|
# @param [WebMock::RequestStub] endpoint_stub the stubbed webmock request
|
23
24
|
#
|
24
|
-
# @return [
|
25
|
+
# @return [Metrics::Endpoint] the stat that was recorded
|
25
26
|
#
|
26
27
|
def self.record(service, endpoint, endpoint_stub)
|
27
28
|
return unless StubRequests.config.record_metrics?
|
@@ -8,7 +8,7 @@
|
|
8
8
|
#
|
9
9
|
module StubRequests
|
10
10
|
#
|
11
|
-
# Module Metrics contains logic for collecting metrics about
|
11
|
+
# Module Metrics contains logic for collecting metrics about requests stubs
|
12
12
|
#
|
13
13
|
# @author Mikael Henriksson <mikael@zoolutions.se>
|
14
14
|
# @since 0.1.2
|
@@ -16,22 +16,22 @@ module StubRequests
|
|
16
16
|
# :reek:TooManyInstanceVariables
|
17
17
|
module Metrics
|
18
18
|
#
|
19
|
-
# Class
|
19
|
+
# Class Endpoint provides metrics for stubbed endpoints
|
20
20
|
#
|
21
21
|
# @author Mikael Henriksson <mikael@zoolutions.se>
|
22
22
|
# @since 0.1.2
|
23
23
|
#
|
24
|
-
class
|
24
|
+
class Endpoint
|
25
25
|
# includes "Enumerable"
|
26
26
|
# @!parse include Enumerable
|
27
27
|
include Enumerable
|
28
|
-
|
29
28
|
# @api private
|
30
29
|
include Property
|
30
|
+
# @api private
|
31
31
|
|
32
32
|
#
|
33
33
|
# @!attribute [r] service_id
|
34
|
-
# @return [Symbol] the id of a {Service}
|
34
|
+
# @return [Symbol] the id of a {StubRequests::Registration::Service}
|
35
35
|
property :service_id, type: Symbol
|
36
36
|
#
|
37
37
|
# @!attribute [r] endpoint_id
|
@@ -44,28 +44,29 @@ module StubRequests
|
|
44
44
|
#
|
45
45
|
# @!attribute [r] uri_template
|
46
46
|
# @return [String] the full URI template for the endpoint
|
47
|
-
property :uri_template, type:
|
47
|
+
property :uri_template, type: String
|
48
48
|
#
|
49
|
-
# @!attribute [r]
|
50
|
-
# @return [Array] an array with recorded
|
51
|
-
attr_reader :
|
49
|
+
# @!attribute [r] stubs
|
50
|
+
# @return [Array] an array with recorded requests
|
51
|
+
attr_reader :requests
|
52
52
|
|
53
53
|
#
|
54
|
-
# Initializes a new
|
54
|
+
# Initializes a new Endpoint
|
55
55
|
#
|
56
|
-
# @param [Service] service a service
|
57
|
-
# @param [Endpoint] endpoint an endpoint
|
56
|
+
# @param [Registration::Service] service a service
|
57
|
+
# @param [Registration::Endpoint] endpoint an endpoint
|
58
58
|
#
|
59
59
|
def initialize(service, endpoint)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
self.service_id = service.id
|
61
|
+
self.endpoint_id = endpoint.id
|
62
|
+
self.verb = endpoint.verb
|
63
|
+
self.uri_template = [service.uri, endpoint.uri_template].join("/")
|
64
|
+
|
65
|
+
@requests = Concurrent::Array.new
|
65
66
|
end
|
66
67
|
|
67
68
|
def find_by(attribute:, value:)
|
68
|
-
find { |
|
69
|
+
find { |request| request.send(attribute) == value }
|
69
70
|
end
|
70
71
|
|
71
72
|
#
|
@@ -77,7 +78,7 @@ module StubRequests
|
|
77
78
|
# @yield used by Enumerable
|
78
79
|
#
|
79
80
|
def each(&block)
|
80
|
-
|
81
|
+
requests.each(&block)
|
81
82
|
end
|
82
83
|
|
83
84
|
#
|
@@ -88,9 +89,9 @@ module StubRequests
|
|
88
89
|
# @return [Record]
|
89
90
|
#
|
90
91
|
def record(request_stub)
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
request = Request.new(self, request_stub)
|
93
|
+
requests.push(request)
|
94
|
+
request
|
94
95
|
end
|
95
96
|
end
|
96
97
|
end
|