vcr 2.0.0.rc1 → 2.0.0.rc2

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.
Files changed (113) hide show
  1. data/.gitignore +2 -0
  2. data/.limited_red +1 -0
  3. data/.travis.yml +10 -1
  4. data/.yardopts +9 -0
  5. data/CHANGELOG.md +51 -1
  6. data/Gemfile +5 -1
  7. data/LICENSE +1 -1
  8. data/README.md +23 -28
  9. data/Rakefile +63 -18
  10. data/Upgrade.md +200 -0
  11. data/features/.nav +2 -0
  12. data/features/cassettes/automatic_re_recording.feature +19 -15
  13. data/features/cassettes/dynamic_erb.feature +12 -4
  14. data/features/cassettes/exclusive.feature +31 -23
  15. data/features/cassettes/format.feature +54 -30
  16. data/features/cassettes/naming.feature +1 -1
  17. data/features/cassettes/update_content_length_header.feature +16 -12
  18. data/features/configuration/allow_http_connections_when_no_cassette.feature +1 -1
  19. data/features/configuration/debug_logging.feature +52 -0
  20. data/features/configuration/filter_sensitive_data.feature +4 -4
  21. data/features/configuration/hook_into.feature +5 -2
  22. data/features/configuration/ignore_request.feature +5 -3
  23. data/features/configuration/preserve_exact_body_bytes.feature +103 -0
  24. data/features/hooks/after_http_request.feature +17 -4
  25. data/features/hooks/around_http_request.feature +2 -1
  26. data/features/hooks/before_http_request.feature +25 -8
  27. data/features/hooks/before_playback.feature +16 -12
  28. data/features/hooks/before_record.feature +2 -2
  29. data/features/http_libraries/em_http_request.feature +82 -58
  30. data/features/http_libraries/net_http.feature +6 -6
  31. data/features/middleware/faraday.feature +2 -1
  32. data/features/middleware/rack.feature +2 -2
  33. data/features/record_modes/all.feature +19 -15
  34. data/features/record_modes/new_episodes.feature +17 -13
  35. data/features/record_modes/none.feature +15 -11
  36. data/features/record_modes/once.feature +16 -12
  37. data/features/request_matching/body.feature +28 -20
  38. data/features/request_matching/custom_matcher.feature +28 -20
  39. data/features/request_matching/headers.feature +34 -26
  40. data/features/request_matching/host.feature +28 -20
  41. data/features/request_matching/identical_request_sequence.feature +28 -20
  42. data/features/request_matching/method.feature +28 -20
  43. data/features/request_matching/path.feature +28 -20
  44. data/features/request_matching/playback_repeats.feature +28 -20
  45. data/features/request_matching/uri.feature +28 -20
  46. data/features/request_matching/uri_without_param.feature +28 -20
  47. data/features/support/env.rb +7 -6
  48. data/features/support/vcr_cucumber_helpers.rb +1 -0
  49. data/features/test_frameworks/cucumber.feature +8 -8
  50. data/features/test_frameworks/rspec_macro.feature +4 -4
  51. data/features/test_frameworks/rspec_metadata.feature +6 -6
  52. data/features/test_frameworks/shoulda.feature +1 -1
  53. data/features/test_frameworks/test_unit.feature +1 -1
  54. data/lib/vcr.rb +156 -5
  55. data/lib/vcr/cassette.rb +80 -30
  56. data/lib/vcr/cassette/http_interaction_list.rb +33 -4
  57. data/lib/vcr/cassette/migrator.rb +2 -3
  58. data/lib/vcr/cassette/reader.rb +1 -0
  59. data/lib/vcr/cassette/serializers.rb +22 -0
  60. data/lib/vcr/cassette/serializers/json.rb +27 -2
  61. data/lib/vcr/cassette/serializers/psych.rb +26 -2
  62. data/lib/vcr/cassette/serializers/syck.rb +28 -2
  63. data/lib/vcr/cassette/serializers/yaml.rb +28 -2
  64. data/lib/vcr/configuration.rb +348 -10
  65. data/lib/vcr/deprecations.rb +8 -0
  66. data/lib/vcr/errors.rb +40 -0
  67. data/lib/vcr/extensions/net_http_response.rb +12 -11
  68. data/lib/vcr/library_hooks.rb +1 -0
  69. data/lib/vcr/library_hooks/excon.rb +24 -3
  70. data/lib/vcr/library_hooks/fakeweb.rb +32 -16
  71. data/lib/vcr/library_hooks/faraday.rb +3 -0
  72. data/lib/vcr/library_hooks/typhoeus.rb +40 -37
  73. data/lib/vcr/library_hooks/webmock.rb +54 -34
  74. data/lib/vcr/middleware/faraday.rb +13 -0
  75. data/lib/vcr/middleware/rack.rb +35 -0
  76. data/lib/vcr/request_handler.rb +60 -8
  77. data/lib/vcr/request_ignorer.rb +1 -0
  78. data/lib/vcr/request_matcher_registry.rb +28 -0
  79. data/lib/vcr/structs.rb +245 -38
  80. data/lib/vcr/test_frameworks/cucumber.rb +10 -0
  81. data/lib/vcr/test_frameworks/rspec.rb +26 -1
  82. data/lib/vcr/util/hooks.rb +29 -27
  83. data/lib/vcr/util/internet_connection.rb +2 -0
  84. data/lib/vcr/util/logger.rb +25 -0
  85. data/lib/vcr/util/variable_args_block_caller.rb +1 -0
  86. data/lib/vcr/util/version_checker.rb +1 -0
  87. data/lib/vcr/version.rb +8 -1
  88. data/spec/capture_warnings.rb +3 -3
  89. data/spec/monkey_patches.rb +28 -13
  90. data/spec/spec_helper.rb +17 -0
  91. data/spec/support/http_library_adapters.rb +7 -4
  92. data/spec/support/shared_example_groups/hook_into_http_library.rb +96 -32
  93. data/spec/support/shared_example_groups/request_hooks.rb +9 -8
  94. data/spec/support/sinatra_app.rb +3 -1
  95. data/spec/support/vcr_localhost_server.rb +1 -0
  96. data/spec/vcr/cassette/http_interaction_list_spec.rb +119 -54
  97. data/spec/vcr/cassette/migrator_spec.rb +19 -6
  98. data/spec/vcr/cassette/serializers_spec.rb +51 -6
  99. data/spec/vcr/cassette_spec.rb +44 -19
  100. data/spec/vcr/configuration_spec.rb +91 -6
  101. data/spec/vcr/library_hooks/excon_spec.rb +54 -16
  102. data/spec/vcr/library_hooks/fakeweb_spec.rb +12 -21
  103. data/spec/vcr/library_hooks/typhoeus_spec.rb +2 -29
  104. data/spec/vcr/library_hooks/webmock_spec.rb +4 -18
  105. data/spec/vcr/middleware/faraday_spec.rb +1 -16
  106. data/spec/vcr/structs_spec.rb +194 -61
  107. data/spec/vcr/test_frameworks/rspec_spec.rb +10 -0
  108. data/spec/vcr/util/hooks_spec.rb +104 -56
  109. data/spec/vcr/util/version_checker_spec.rb +45 -0
  110. data/spec/vcr_spec.rb +11 -0
  111. data/vcr.gemspec +30 -34
  112. metadata +149 -95
  113. data/spec/support/shared_example_groups/version_checking.rb +0 -34
data/features/.nav CHANGED
@@ -24,6 +24,8 @@
24
24
  - ignore_request.feature
25
25
  - filter_sensitive_data.feature
26
26
  - allow_http_connections_when_no_cassette.feature
27
+ - debug_logging.feature
28
+ - preserve_exact_body_bytes.feature
27
29
  - hooks:
28
30
  - before_record.feature
29
31
  - before_playback.feature
@@ -13,22 +13,26 @@ Feature: Automatic Re-recording
13
13
  Background:
14
14
  Given a previously recorded cassette file "cassettes/example.yml" with:
15
15
  """
16
- ---
17
- http_interactions:
18
- - request:
16
+ ---
17
+ http_interactions:
18
+ - request:
19
19
  method: get
20
20
  uri: http://localhost:7777/
21
- body: ''
21
+ body:
22
+ encoding: UTF-8
23
+ string: ""
22
24
  headers: {}
23
- response:
24
- status:
25
+ response:
26
+ status:
25
27
  code: 200
26
28
  message: OK
27
- headers:
28
- Content-Length:
29
- - '12'
30
- body: Old Response
31
- http_version: '1.1'
29
+ headers:
30
+ Content-Length:
31
+ - "12"
32
+ body:
33
+ encoding: UTF-8
34
+ string: Old Response
35
+ http_version: "1.1"
32
36
  recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
33
37
  recorded_with: VCR 2.0.0
34
38
  """
@@ -55,14 +59,14 @@ Feature: Automatic Re-recording
55
59
  When I run `ruby re_record.rb`
56
60
  Then the output should contain "Old Response"
57
61
  But the output should not contain "New Response"
58
- And the file "cassettes/example.yml" should contain "body: Old Response"
59
- But the file "cassettes/example.yml" should not contain "body: New Response"
62
+ And the file "cassettes/example.yml" should contain "Old Response"
63
+ But the file "cassettes/example.yml" should not contain "New Response"
60
64
 
61
65
  Scenario: Cassette is re-recorded when enough time has passed
62
66
  Given it is Tue, 09 Nov 2011
63
67
  When I run `ruby re_record.rb`
64
68
  Then the output should contain "New Response"
65
69
  But the output should not contain "Old Response"
66
- And the file "cassettes/example.yml" should contain "body: New Response"
67
- But the file "cassettes/example.yml" should not contain "body: Old Response"
70
+ And the file "cassettes/example.yml" should contain "New Response"
71
+ But the file "cassettes/example.yml" should not contain "Old Response"
68
72
 
@@ -17,7 +17,9 @@ Feature: Dynamic ERB cassettes
17
17
  - request:
18
18
  method: get
19
19
  uri: http://example.com/foo?a=<%= 'b' * 3 %>
20
- body: ''
20
+ body:
21
+ encoding: UTF-8
22
+ string: ''
21
23
  headers: {}
22
24
  response:
23
25
  status:
@@ -28,7 +30,9 @@ Feature: Dynamic ERB cassettes
28
30
  - text/html;charset=utf-8
29
31
  Content-Length:
30
32
  - '9'
31
- body: Hello <%= 'bar'.next %>
33
+ body:
34
+ encoding: UTF-8
35
+ string: Hello <%= 'bar'.next %>
32
36
  http_version: '1.1'
33
37
  recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
34
38
  recorded_with: VCR 2.0.0
@@ -58,7 +62,9 @@ Feature: Dynamic ERB cassettes
58
62
  - request:
59
63
  method: get
60
64
  uri: http://example.com/foo?a=<%= arg1 %>
61
- body: ''
65
+ body:
66
+ encoding: UTF-8
67
+ string: ''
62
68
  headers: {}
63
69
  response:
64
70
  status:
@@ -69,7 +75,9 @@ Feature: Dynamic ERB cassettes
69
75
  - text/html;charset=utf-8
70
76
  Content-Length:
71
77
  - '9'
72
- body: Hello <%= arg2 %>
78
+ body:
79
+ encoding: UTF-8
80
+ string: Hello <%= arg2 %>
73
81
  http_version: '1.1'
74
82
  recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
75
83
  recorded_with: VCR 2.0.0
@@ -17,43 +17,51 @@ Feature: exclusive cassette
17
17
  Background:
18
18
  Given a previously recorded cassette file "cassettes/outer.yml" with:
19
19
  """
20
- ---
21
- http_interactions:
22
- - request:
20
+ ---
21
+ http_interactions:
22
+ - request:
23
23
  method: get
24
24
  uri: http://localhost:7777/outer
25
- body: ''
25
+ body:
26
+ encoding: UTF-8
27
+ string: ""
26
28
  headers: {}
27
- response:
28
- status:
29
+ response:
30
+ status:
29
31
  code: 200
30
32
  message: OK
31
- headers:
32
- Content-Length:
33
- - '18'
34
- body: Old outer response
35
- http_version: '1.1'
33
+ headers:
34
+ Content-Length:
35
+ - "18"
36
+ body:
37
+ encoding: UTF-8
38
+ string: Old outer response
39
+ http_version: "1.1"
36
40
  recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
37
41
  recorded_with: VCR 2.0.0
38
42
  """
39
43
  And a previously recorded cassette file "cassettes/inner.yml" with:
40
44
  """
41
- ---
42
- http_interactions:
43
- - request:
45
+ ---
46
+ http_interactions:
47
+ - request:
44
48
  method: get
45
49
  uri: http://localhost:7777/inner
46
- body: ''
50
+ body:
51
+ encoding: UTF-8
52
+ string: ""
47
53
  headers: {}
48
- response:
49
- status:
54
+ response:
55
+ status:
50
56
  code: 200
51
57
  message: OK
52
- headers:
53
- Content-Length:
54
- - '18'
55
- body: Old inner response
56
- http_version: '1.1'
58
+ headers:
59
+ Content-Length:
60
+ - "18"
61
+ body:
62
+ encoding: UTF-8
63
+ string: Old inner response
64
+ http_version: "1.1"
57
65
  recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
58
66
  recorded_with: VCR 2.0.0
59
67
  """
@@ -111,5 +119,5 @@ Feature: exclusive cassette
111
119
  New outer response
112
120
  Old inner response
113
121
  """
114
- And the file "cassettes/inner.yml" should contain "body: New outer response"
122
+ And the file "cassettes/inner.yml" should contain "New outer response"
115
123
 
@@ -9,6 +9,8 @@ Feature: Cassette format
9
9
  - method
10
10
  - uri
11
11
  - body
12
+ - encoding
13
+ - string
12
14
  - headers
13
15
  - response
14
16
  - status
@@ -16,6 +18,8 @@ Feature: Cassette format
16
18
  - message
17
19
  - headers
18
20
  - body
21
+ - encoding
22
+ - string
19
23
  - http version
20
24
 
21
25
  By default, VCR uses YAML to serialize this data. You can configure
@@ -72,41 +76,49 @@ Feature: Cassette format
72
76
  When I run `ruby cassette_yaml.rb 'Hello'`
73
77
  Then the file "cassettes/example.yml" should contain YAML like:
74
78
  """
75
- ---
76
- http_interactions:
77
- - request:
79
+ ---
80
+ http_interactions:
81
+ - request:
78
82
  method: get
79
83
  uri: http://localhost:7777/foo
80
- body: ''
84
+ body:
85
+ encoding: UTF-8
86
+ string: ""
81
87
  headers: {}
82
- response:
83
- status:
88
+ response:
89
+ status:
84
90
  code: 200
85
91
  message: OK
86
- headers:
87
- Content-Type:
92
+ headers:
93
+ Content-Type:
88
94
  - text/html;charset=utf-8
89
- Content-Length:
90
- - '9'
91
- body: Hello foo
92
- http_version: '1.1'
95
+ Content-Length:
96
+ - "9"
97
+ body:
98
+ encoding: UTF-8
99
+ string: Hello foo
100
+ http_version: "1.1"
93
101
  recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
94
- - request:
102
+ - request:
95
103
  method: get
96
104
  uri: http://localhost:7777/bar
97
- body: ''
105
+ body:
106
+ encoding: UTF-8
107
+ string: ""
98
108
  headers: {}
99
- response:
100
- status:
109
+ response:
110
+ status:
101
111
  code: 200
102
112
  message: OK
103
- headers:
104
- Content-Type:
113
+ headers:
114
+ Content-Type:
105
115
  - text/html;charset=utf-8
106
- Content-Length:
107
- - '9'
108
- body: Hello bar
109
- http_version: '1.1'
116
+ Content-Length:
117
+ - "9"
118
+ body:
119
+ encoding: UTF-8
120
+ string: Hello bar
121
+ http_version: "1.1"
110
122
  recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
111
123
  recorded_with: VCR 2.0.0
112
124
  """
@@ -152,7 +164,10 @@ Feature: Cassette format
152
164
  "http_interactions": [
153
165
  {
154
166
  "response": {
155
- "body": "Hello foo",
167
+ "body": {
168
+ "encoding": "UTF-8",
169
+ "string": "Hello foo"
170
+ },
156
171
  "http_version": null,
157
172
  "status": { "code": 200, "message": "OK" },
158
173
  "headers": {
@@ -165,7 +180,10 @@ Feature: Cassette format
165
180
  },
166
181
  "request": {
167
182
  "uri": "http://localhost:7777/foo",
168
- "body": "",
183
+ "body": {
184
+ "encoding": "UTF-8",
185
+ "string": ""
186
+ },
169
187
  "method": "get",
170
188
  "headers": { }
171
189
  },
@@ -173,7 +191,10 @@ Feature: Cassette format
173
191
  },
174
192
  {
175
193
  "response": {
176
- "body": "Hello bar",
194
+ "body": {
195
+ "encoding": "UTF-8",
196
+ "string": "Hello bar"
197
+ },
177
198
  "http_version": null,
178
199
  "status": { "code": 200, "message": "OK" },
179
200
  "headers": {
@@ -186,7 +207,10 @@ Feature: Cassette format
186
207
  },
187
208
  "request": {
188
209
  "uri": "http://localhost:7777/bar",
189
- "body": "",
210
+ "body": {
211
+ "encoding": "UTF-8",
212
+ "string": ""
213
+ },
190
214
  "method": "get",
191
215
  "headers": { }
192
216
  },
@@ -241,7 +265,7 @@ Feature: Cassette format
241
265
  [{"request"=>
242
266
  {"method"=>"get",
243
267
  "uri"=>"http://localhost:7777/foo",
244
- "body"=>"",
268
+ "body"=>{"encoding"=>"UTF-8", "string"=>""},
245
269
  "headers"=>{"Accept"=>["*/*"], "User-Agent"=>["Ruby"]}},
246
270
  "response"=>
247
271
  {"status"=>{"code"=>200, "message"=>"OK "},
@@ -249,13 +273,13 @@ Feature: Cassette format
249
273
  {"Content-Type"=>["text/html;charset=utf-8"],
250
274
  "Content-Length"=>["9"],
251
275
  "Connection"=>["Keep-Alive"]},
252
- "body"=>"Hello foo",
276
+ "body"=>{"encoding"=>"UTF-8", "string"=>"Hello foo"},
253
277
  "http_version"=>nil},
254
278
  "recorded_at"=>"Tue, 01 Nov 2011 04:58:44 GMT"},
255
279
  {"request"=>
256
280
  {"method"=>"get",
257
281
  "uri"=>"http://localhost:7777/bar",
258
- "body"=>"",
282
+ "body"=>{"encoding"=>"UTF-8", "string"=>""},
259
283
  "headers"=>{"Accept"=>["*/*"], "User-Agent"=>["Ruby"]}},
260
284
  "response"=>
261
285
  {"status"=>{"code"=>200, "message"=>"OK "},
@@ -263,7 +287,7 @@ Feature: Cassette format
263
287
  {"Content-Type"=>["text/html;charset=utf-8"],
264
288
  "Content-Length"=>["9"],
265
289
  "Connection"=>["Keep-Alive"]},
266
- "body"=>"Hello bar",
290
+ "body"=>{"encoding"=>"UTF-8", "string"=>"Hello bar"},
267
291
  "http_version"=>nil},
268
292
  "recorded_at"=>"Tue, 01 Nov 2011 04:58:44 GMT"}],
269
293
  "recorded_with"=>"VCR 2.0.0"}
@@ -24,4 +24,4 @@ Feature: Naming
24
24
  """
25
25
  And the directory "cassettes" does not exist
26
26
  When I run `ruby name_sanitizing.rb`
27
- Then the file "cassettes/Fee_Fi_Fo_Fum.yml" should contain "body: Hello"
27
+ Then the file "cassettes/Fee_Fi_Fo_Fum.yml" should contain "Hello"
@@ -21,24 +21,28 @@ Feature: Update content_length header
21
21
  Background:
22
22
  Given a previously recorded cassette file "cassettes/example.yml" with:
23
23
  """
24
- ---
25
- http_interactions:
26
- - request:
24
+ ---
25
+ http_interactions:
26
+ - request:
27
27
  method: get
28
28
  uri: http://example.com/
29
- body: ''
29
+ body:
30
+ encoding: UTF-8
31
+ string: ""
30
32
  headers: {}
31
- response:
32
- status:
33
+ response:
34
+ status:
33
35
  code: 200
34
36
  message: OK
35
- headers:
36
- Content-Type:
37
+ headers:
38
+ Content-Type:
37
39
  - text/html;charset=utf-8
38
- Content-Length:
39
- - '11'
40
- body: Hello <modified>
41
- http_version: '1.1'
40
+ Content-Length:
41
+ - "11"
42
+ body:
43
+ encoding: UTF-8
44
+ string: Hello <modified>
45
+ http_version: "1.1"
42
46
  recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
43
47
  recorded_with: VCR 2.0.0
44
48
  """
@@ -45,7 +45,7 @@ Feature: Allow HTTP connections when no cassette
45
45
  """
46
46
  When I run `ruby record_replay_cassette.rb --with-server`
47
47
  Then the output should contain "Response: Hello"
48
- And the file "cassettes/localhost.yml" should contain "body: Hello"
48
+ And the file "cassettes/localhost.yml" should contain "Hello"
49
49
 
50
50
  When I run `ruby record_replay_cassette.rb`
51
51
  Then the output should contain "Response: Hello"
@@ -0,0 +1,52 @@
1
+ @exclude-18
2
+ Feature: Debug Logging
3
+
4
+ Use the `debug_logger` option to set an IO-like object that VCR will log
5
+ debug output to. This is a useful way to troubleshoot what VCR is doing.
6
+
7
+ The debug logger must respond to `#puts`.
8
+
9
+ Scenario: Use the debug logger for troubleshooting
10
+ Given a file named "debug_logger.rb" with:
11
+ """ruby
12
+ if ARGV.include?('--with-server')
13
+ start_sinatra_app(:port => 7777) do
14
+ get('/') { "Hello World" }
15
+ end
16
+ end
17
+
18
+ require 'vcr'
19
+
20
+ VCR.configure do |c|
21
+ c.hook_into :fakeweb
22
+ c.cassette_library_dir = 'cassettes'
23
+ c.debug_logger = File.open(ARGV.first, 'w')
24
+ end
25
+
26
+ VCR.use_cassette('example') do
27
+ Net::HTTP.get_response(URI("http://localhost:7777/"))
28
+ end
29
+ """
30
+ When I run `ruby debug_logger.rb record.log --with-server`
31
+ Then the file "record.log" should contain exactly:
32
+ """
33
+ [Cassette: 'example'] Initialized with options: {:record=>:once, :match_requests_on=>[:method, :uri], :serialize_with=>:yaml}
34
+ [fakeweb] Handling request: [get http://localhost:7777/] (disabled: false)
35
+ [Cassette: 'example'] Initialized HTTPInteractionList with request matchers [:method, :uri] and 0 interaction(s): { }
36
+ [fakeweb] Identified request type (recordable) for [get http://localhost:7777/]
37
+ [Cassette: 'example'] Recorded HTTP interaction [get http://localhost:7777/] => [200 "Hello World"]
38
+
39
+ """
40
+ When I run `ruby debug_logger.rb playback.log`
41
+ Then the file "playback.log" should contain exactly:
42
+ """
43
+ [Cassette: 'example'] Initialized with options: {:record=>:once, :match_requests_on=>[:method, :uri], :serialize_with=>:yaml}
44
+ [fakeweb] Handling request: [get http://localhost:7777/] (disabled: false)
45
+ [Cassette: 'example'] Initialized HTTPInteractionList with request matchers [:method, :uri] and 1 interaction(s): { [get http://localhost:7777/] => [200 "Hello World"] }
46
+ [Cassette: 'example'] Checking if [get http://localhost:7777/] matches [get http://localhost:7777/] using [:method, :uri]
47
+ [Cassette: 'example'] method (matched): current request [get http://localhost:7777/] vs [get http://localhost:7777/]
48
+ [Cassette: 'example'] uri (matched): current request [get http://localhost:7777/] vs [get http://localhost:7777/]
49
+ [Cassette: 'example'] Found matching interaction for [get http://localhost:7777/] at index 0: [200 "Hello World"]
50
+ [fakeweb] Identified request type (stubbed) for [get http://localhost:7777/]
51
+
52
+ """