├── .gitignore ├── LICENSE ├── README.md ├── VCRSharp.Benchmarks ├── Program.cs ├── VCRSharp.Benchmarks.csproj └── fixtures │ └── example-test.json ├── appveyor.yml ├── vcr-sharp-tests ├── CacheResult.cs ├── Cassette.cs ├── HttpClientFactory.cs ├── PlaybackException.cs ├── ReplayingHandler.cs ├── ScratchPad.cs ├── Serializer.cs ├── SerializerTests.cs ├── VCRSharp.Tests.csproj └── fixtures │ ├── example-test.json │ └── overwrite-request.json └── vcr-sharp.sln /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | obj/ 3 | bin/ 4 | 5 | BenchmarkDotNet.Artifacts/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Brendan Forster 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vcr-sharp 2 | 3 | A library for supporting recording and replaying HTTP requests when using [`HttpClient`](https://www.nuget.org/packages/System.Net.Http). 4 | 5 | ## Installation 6 | 7 | Not just yet, gotta make it work better. 8 | 9 | ## Usage 10 | 11 | Rather than creating a `HttpClient` manually, your test should use the `HttpClientFactory` 12 | with a provided **cassette** to represent the context of the test. 13 | 14 | This cassette file is a JSON file on disk, which is used to store cached HTTP requests. 15 | 16 | You can then use the received `HttpClient` instance like you would normally: 17 | 18 | ```cs 19 | using (var httpClient = HttpClientFactory.WithCassette("my-test-scenario")) 20 | { 21 | var request = new HttpRequestMessage(HttpMethod.Get, "http://www.iana.org/domains/reserved"); 22 | var response = await httpClient.SendAsync(request); 23 | var body = await response.Content.ReadAsStringAsync(); 24 | body.ShouldContain("Example domains"); 25 | } 26 | ``` 27 | 28 | VCR supports a number of modes, which are specified by environment variables to 29 | simplify maintenance of tests: 30 | 31 | - `playback` (default) - always use the cache, throw an error if not found in cache 32 | - `cache` - try the cache, fallback to the network if not found and add the new response 33 | - `record` - avoid the cache, always use the network and store the new response 34 | 35 | Simply set the `VCR_MODE` environment variable to whatever mode you want when running the tests. 36 | 37 | ## Why not use `XYZ`? 38 | 39 | I evaluated `scotch` before and wanted to take it further but ended up starting clean for a 40 | couple of reasons: 41 | 42 | - the use of F# - I wasn't in the mood to re-learn F#, I just wanted to get this idea out there 43 | - putting the replay/recording state in the API isn't something I'm a fan of - I wanted to 44 | instead use environment variables so the tests remained simpler. 45 | 46 | ## Credits 47 | 48 | While it's early days (and I'm not sure if this will even reach production usage) there's already 49 | a lot of prior art in here which I've used for research as part of starting this: 50 | 51 | - The contributors to the [`vcr`](https://github.com/vcr/vcr) gem 52 | - [**@philschatz**](https://github.com/philschatz) for his work on [`fetch-vcr`](https://github.com/philschatz/fetch-vcr) 53 | - [**@mleech**](https://github.com/mleech) for his work on [`scotch`](https://github.com/mleech/scotch) 54 | 55 | ## TODO 56 | 57 | - probably a bunch of other things 58 | -------------------------------------------------------------------------------- /VCRSharp.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using System; 3 | using BenchmarkDotNet.Running; 4 | using VcrSharp.Tests; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | 8 | namespace VCRSharp.Benchmarks 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | var summary = BenchmarkRunner.Run(); 15 | } 16 | } 17 | 18 | public class VCRSharpBenchmarks : IDisposable 19 | { 20 | public VCRSharpBenchmarks() 21 | { 22 | Environment.SetEnvironmentVariable("VCR_MODE", "playback"); 23 | } 24 | 25 | [Benchmark] 26 | public async Task ReadFromCache() 27 | { 28 | 29 | using (var httpClient = HttpClientFactory.WithCassette("example-test")) 30 | { 31 | var request = new HttpRequestMessage(HttpMethod.Get, "http://www.iana.org/domains/reserved"); 32 | var response = await httpClient.SendAsync(request); 33 | } 34 | } 35 | 36 | private bool disposedValue = false; 37 | 38 | protected virtual void Dispose(bool disposing) 39 | { 40 | if (!disposedValue) 41 | { 42 | if (disposing) 43 | { 44 | Environment.SetEnvironmentVariable("VCR_MODE", ""); 45 | } 46 | 47 | disposedValue = true; 48 | } 49 | } 50 | public void Dispose() 51 | { 52 | Dispose(true); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /VCRSharp.Benchmarks/VCRSharp.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Always 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /VCRSharp.Benchmarks/fixtures/example-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "http_interactions": [ 3 | { 4 | "request": { 5 | "method": "get", 6 | "uri": "http://www.iana.org/domains/reserved", 7 | "body": { 8 | "encoding": "US-ASCII", 9 | "base64_string": "" 10 | }, 11 | "headers": { 12 | "Accept-Encoding": [ 13 | "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" 14 | ], 15 | "Accept": [ 16 | "*/*" 17 | ], 18 | "User-Agent": [ 19 | "Ruby" 20 | ], 21 | "Host": [ 22 | "www.iana.org" 23 | ] 24 | } 25 | }, 26 | "response": { 27 | "status": { 28 | "code": 200, 29 | "message": "OK" 30 | }, 31 | "headers": { 32 | "Date": [ 33 | "Wed, 06 Sep 2017 10:32:27 GMT" 34 | ], 35 | "Content-Length": [ 36 | "3093" 37 | ], 38 | "Vary": [ 39 | "Accept-Encoding" 40 | ], 41 | "Last-Modified": [ 42 | "Tue, 21 Jul 2015 00:49:48 GMT" 43 | ], 44 | "Expires": [ 45 | "Wed, 06 Sep 2017 12:30:59 GMT" 46 | ], 47 | "X-Frame-Options": [ 48 | "SAMEORIGIN" 49 | ], 50 | "Content-Security-Policy": [ 51 | "upgrade-insecure-requests" 52 | ], 53 | "Content-Type": [ 54 | "text/html; charset=UTF-8" 55 | ], 56 | "Server": [ 57 | "Apache" 58 | ], 59 | "Strict-Transport-Security": [ 60 | "max-age=47304003; preload" 61 | ], 62 | "X-Cache-Hits": [ 63 | "26" 64 | ], 65 | "Accept-Ranges": [ 66 | "bytes" 67 | ], 68 | "Connection": [ 69 | "keep-alive" 70 | ] 71 | }, 72 | "body": { 73 | "encoding": "ASCII-8BIT", 74 | "base64_string": "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgo8aGVhZD4KCTx0aXRsZT5JQU5BIOKA\nlCBJQU5BLW1hbmFnZWQgUmVzZXJ2ZWQgRG9tYWluczwvdGl0bGU+CgoJPG1l\ndGEgY2hhcnNldD0idXRmLTgiIC8+Cgk8bWV0YSBodHRwLWVxdWl2PSJDb250\nZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIg\nLz4KCTxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZp\nY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSIgLz4KCQoJPGxpbmsgcmVsPSJz\ndHlsZXNoZWV0IiBtZWRpYT0ic2NyZWVuIiBocmVmPSIvX2Nzcy8yMDE1LjEv\nc2NyZWVuLmNzcyIvPgoJPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBtZWRpYT0i\ncHJpbnQiIGhyZWY9Ii9fY3NzLzIwMTUuMS9wcmludC5jc3MiLz4KCTxsaW5r\nIHJlbD0ic2hvcnRjdXQgaWNvbiIgdHlwZT0iaW1hZ2UvaWNvIiBocmVmPSIv\nX2ltZy9ib29rbWFya19pY29uLmljbyIvPgoKCTxzY3JpcHQgdHlwZT0idGV4\ndC9qYXZhc2NyaXB0IiBzcmM9Ii9fanMvMjAxMy4xL2pxdWVyeS5qcyI+PC9z\nY3JpcHQ+Cgk8c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCIgc3JjPSIv\nX2pzLzIwMTMuMS9pYW5hLmpzIj48L3NjcmlwdD4KCgkKCjwvaGVhZD4KCjxi\nb2R5PgoJCgk8aGVhZGVyPgoJCTxkaXYgaWQ9ImhlYWRlciI+CgkJCTxkaXYg\naWQ9ImxvZ28iPgoJCQkJPGEgaHJlZj0iLyI+PGltZyBzcmM9Ii9faW1nLzIw\nMTMuMS9pYW5hLWxvZ28taGVhZGVyLnN2ZyIgYWx0PSJIb21lcGFnZSIvPjwv\nYT4KCQkJPC9kaXY+CgkJCTxkaXYgY2xhc3M9Im5hdmlnYXRpb24iPgoJCQkJ\nPHVsPgoJCQkJCTxsaT48YSBocmVmPSIvZG9tYWlucyI+RG9tYWluczwvYT48\nL2xpPgoJCQkJCTxsaT48YSBocmVmPSIvbnVtYmVycyI+TnVtYmVyczwvYT48\nL2xpPgoJCQkJCTxsaT48YSBocmVmPSIvcHJvdG9jb2xzIj5Qcm90b2NvbHM8\nL2E+PC9saT4KCQkJCQk8bGk+PGEgaHJlZj0iL2Fib3V0Ij5BYm91dCBVczwv\nYT48L2xpPgoJCQkJPC91bD4KCQkJPC9kaXY+CgkJPC9kaXY+Cgk8L2hlYWRl\ncj4KCQoJPGRpdiBpZD0iYm9keSI+CgkKCgkJCTxkaXYgaWQ9Im1haW5fcmln\naHQiPgoKCgk8aDE+SUFOQS1tYW5hZ2VkIFJlc2VydmVkIERvbWFpbnM8L2gx\nPgoKCTxwPkNlcnRhaW4gZG9tYWlucyBhcmUgc2V0IGFzaWRlLCBhbmQgbm9t\naW5hbGx5IHJlZ2lzdGVyZWQgdG8gJmxkcXVvO0lBTkEmcmRxdW87LCBmb3Ig\nc3BlY2lmaWMKCQlwb2xpY3kgb3IgdGVjaG5pY2FsIHB1cnBvc2VzLjwvcD4K\nCgk8aDI+RXhhbXBsZSBkb21haW5zPC9oMj4KCQoJPHA+QXMgZGVzY3JpYmVk\nIGluIDxhIGhyZWY9Ii9nby9yZmMyNjA2Ij5SRkMgMjYwNjwvYT4gYW5kIDxh\nIGhyZWY9Ii9nby9yZmM2NzYxIj5SRkMgNjc2MTwvYT4sCglhIG51bWJlciBv\nZiBkb21haW5zIHN1Y2ggYXMgPHNwYW4gY2xhc3M9ImRvbWFpbiBsYWJlbCI+\nZXhhbXBsZS5jb208L3NwYW4+IGFuZCA8c3BhbiBjbGFzcz0iZG9tYWluIGxh\nYmVsIj5leGFtcGxlLm9yZzwvc3Bhbj4KCWFyZSBtYWludGFpbmVkIGZvciBk\nb2N1bWVudGF0aW9uIHB1cnBvc2VzLiBUaGVzZSBkb21haW5zIG1heSBiZSB1\nc2VkIGFzIGlsbHVzdHJhdGl2ZQoJZXhhbXBsZXMgaW4gZG9jdW1lbnRzIHdp\ndGhvdXQgcHJpb3IgY29vcmRpbmF0aW9uIHdpdGggdXMuIFRoZXkgYXJlIAoJ\nbm90IGF2YWlsYWJsZSBmb3IgcmVnaXN0cmF0aW9uIG9yIHRyYW5zZmVyLjwv\ncD4KCgk8aDI+VGVzdCBJRE4gdG9wLWxldmVsIGRvbWFpbnM8L2gyPgoKCSAg\nICAgICAgPHA+VGhlc2UgZG9tYWlucyB3ZXJlIHRlbXBvcmFyaWx5IGRlbGVn\nYXRlZCBieSBJQU5BIGZvciB0aGUgPGEgaHJlZj0iaHR0cDovL3d3dy5pY2Fu\nbi5vcmcvdG9waWNzL2lkbi8iPklETiBFdmFsdWF0aW9uPC9hPiBiZWluZyBj\nb25kdWN0ZWQgYnkgPGEgaHJlZj0iaHR0cDovL3d3dy5pY2Fubi5vcmcvIj5J\nQ0FOTjwvYT4uPC9wPgoKCQk8ZGl2IGNsYXNzPSJpYW5hLXRhYmxlLWZyYW1l\nIj4KCQk8dGFibGUgaWQ9ImFycGEtdGFibGUiIGNsYXNzPSJpYW5hLXRhYmxl\nIj4KCQkJPHRoZWFkPgoJCQk8dHI+PHRoPkRvbWFpbjwvdGg+PHRoPkRvbWFp\nbiAoQS1sYWJlbCk8L3RoPjx0aD5MYW5ndWFnZTwvdGg+PHRoPlNjcmlwdDwv\ndGg+PC90cj4KCQkJPC90aGVhZD4KCQkJPHRib2R5PgoJCQk8dHI+PHRkPiYj\nMTU3MzsmIzE1ODI7JiMxNTc4OyYjMTU3NjsmIzE1NzU7JiMxNTg1OzwvdGQ+\nPHRkPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21h\naW5zL3Jvb3QvZGIveG4tLWtnYmVjaHR2Lmh0bWwiPlhOLS1LR0JFQ0hUVjwv\nYT48L3NwYW4+PC90ZD4KCTx0ZD5BcmFiaWM8L3RkPjx0ZD5BcmFiaWM8L3Rk\nPjwvdHI+Cgk8dHI+PHRkPiYjMTU3MDsmIzE1ODY7JiMxNjA1OyYjMTU3NTsm\nIzE3NDA7JiMxNTg4OyYjMTc0MDs8L3RkPjx0ZD48c3BhbiBjbGFzcz0iZG9t\nYWluIGxhYmVsIj48YSBocmVmPSIvZG9tYWlucy9yb290L2RiL3huLS1oZ2Jr\nNmFqN2Y1M2JiYS5odG1sIj5YTi0tSEdCSzZBSjdGNTNCQkE8L2E+PC9zcGFu\nPjwvdGQ+Cgk8dGQ+UGVyc2lhbjwvdGQ+PHRkPkFyYWJpYzwvdGQ+PC90cj4K\nCTx0cj48dGQ+JiMyNzk3OTsmIzM1Nzk3OzwvdGQ+PHRkPjxzcGFuIGNsYXNz\nPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QvZGIveG4t\nLTB6d201NmQuaHRtbCI+WE4tLTBaV001NkQ8L2E+PC9zcGFuPjwvdGQ+Cgk8\ndGQ+Q2hpbmVzZTwvdGQ+PHRkPkhhbiAoU2ltcGxpZmllZCB2YXJpYW50KTwv\ndGQ+PC90cj4KCTx0cj48dGQ+JiMyODIwNDsmIzM1NDMwOzwvdGQ+PHRkPjxz\ncGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21haW5zL3Jv\nb3QvZGIveG4tLWc2dzI1MWQuaHRtbCI+WE4tLUc2VzI1MUQ8L2E+PC9zcGFu\nPjwvdGQ+Cgk8dGQ+Q2hpbmVzZTwvdGQ+PHRkPkhhbiAoVHJhZGl0aW9uYWwg\ndmFyaWFudCk8L3RkPjwvdHI+Cgk8dHI+PHRkPiYjMTA4MDsmIzEwODk7JiMx\nMDg3OyYjMTA5OTsmIzEwOTA7JiMxMDcyOyYjMTA4NTsmIzEwODA7JiMxMDc3\nOzwvdGQ+PHRkPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9\nIi9kb21haW5zL3Jvb3QvZGIveG4tLTgwYWtoYnlrbmo0Zi5odG1sIj5YTi0t\nODBBS0hCWUtOSjRGPC9hPjwvc3Bhbj48L3RkPgoJPHRkPlJ1c3NpYW48L3Rk\nPjx0ZD5DeXJpbGxpYzwvdGQ+PC90cj4KCTx0cj48dGQ+JiMyMzQ2OyYjMjM1\nMjsmIzIzNjg7JiMyMzI1OyYjMjM4MTsmIzIzNTk7JiMyMzY2OzwvdGQ+PHRk\nPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21haW5z\nL3Jvb3QvZGIveG4tLTExYjViczNhOWFqNmcuaHRtbCI+WE4tLTExQjVCUzNB\nOUFKNkc8L2E+PC9zcGFuPjwvdGQ+Cgk8dGQ+SGluZGk8L3RkPjx0ZD5EZXZh\nbmFnYXJpIChOYWdhcmkpPC90ZD48L3RyPgoJPHRyPjx0ZD4mIzk0ODsmIzk1\nOTsmIzk1NDsmIzk1MzsmIzk1NjsmIzk0Mjs8L3RkPjx0ZD48c3BhbiBjbGFz\ncz0iZG9tYWluIGxhYmVsIj48YSBocmVmPSIvZG9tYWlucy9yb290L2RiL3hu\nLS1qeGFscGRscC5odG1sIj5YTi0tSlhBTFBETFA8L2E+PC9zcGFuPjwvdGQ+\nCgk8dGQ+R3JlZWssIE1vZGVybiAoMTQ1My0pPC90ZD48dGQ+R3JlZWs8L3Rk\nPjwvdHI+Cgk8dHI+PHRkPiYjNTM1ODA7JiM0OTgyODsmIzUzOTQ0OzwvdGQ+\nPHRkPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21h\naW5zL3Jvb3QvZGIveG4tLTl0NGIxMXlpNWEuaHRtbCI+WE4tLTlUNEIxMVlJ\nNUE8L2E+PC9zcGFuPjwvdGQ+Cgk8dGQ+S29yZWFuPC90ZD48dGQ+SGFuZ3Vs\nIChIYW5nJiN4MTZEO2wsIEhhbmdldWwpPC90ZD48L3RyPgoJPHRyPjx0ZD4m\nIzE0OTY7JiMxNTA2OyYjMTUwNTsmIzE0OTY7PC90ZD48dGQ+PHNwYW4gY2xh\nc3M9ImRvbWFpbiBsYWJlbCI+PGEgaHJlZj0iL2RvbWFpbnMvcm9vdC9kYi94\nbi0tZGViYTBhZC5odG1sIj5YTi0tREVCQTBBRDwvYT48L3NwYW4+PC90ZD4K\nCTx0ZD5ZaWRkaXNoPC90ZD48dGQ+SGVicmV3PC90ZD48L3RyPgoJPHRyPjx0\nZD4mIzEyNDg2OyYjMTI0NzM7JiMxMjQ4ODs8L3RkPjx0ZD48c3BhbiBjbGFz\ncz0iZG9tYWluIGxhYmVsIj48YSBocmVmPSIvZG9tYWlucy9yb290L2RiL3hu\nLS16Y2t6YWguaHRtbCI+WE4tLVpDS1pBSDwvYT48L3NwYW4+PC90ZD4KCTx0\nZD5KYXBhbmVzZTwvdGQ+PHRkPkthdGFrYW5hPC90ZD48L3RyPgoJPHRyPjx0\nZD4mIzI5ODY7JiMyOTkyOyYjMzAwNzsmIzI5NzU7JiMzMDIxOyYjMjk3MDsm\nIzMwMTY7PC90ZD48dGQ+PHNwYW4gY2xhc3M9ImRvbWFpbiBsYWJlbCI+PGEg\naHJlZj0iL2RvbWFpbnMvcm9vdC9kYi94bi0taGxjajZheWE5ZXNjN2EuaHRt\nbCI+WE4tLUhMQ0o2QVlBOUVTQzdBPC9hPjwvc3Bhbj48L3RkPgoJPHRkPlRh\nbWlsPC90ZD48dGQ+VGFtaWw8L3RkPjwvdHI+PC90Ym9keT4KCQk8L3RhYmxl\nPgoJICAgICAgICA8L2Rpdj4KCgk8aDI+UG9saWN5LXJlc2VydmVkIGRvbWFp\nbnM8L2gyPgoJCgk8cD5XZSBhY3QgYXMgYm90aCB0aGUgcmVnaXN0cmFudCBh\nbmQgcmVnaXN0cmFyIGZvciBhIHNlbGVjdCBudW1iZXIgb2YgZG9tYWlucwoJ\nCXdoaWNoIGhhdmUgYmVlbiByZXNlcnZlZCB1bmRlciBwb2xpY3kgZ3JvdW5k\ncy4gVGhlc2UgZXhjbHVzaW9ucyBhcmUKCQl0eXBpY2FsbHkgaW5kaWNhdGVk\nIGluIGVpdGhlciB0ZWNobmljYWwgc3RhbmRhcmRzIChSRkMgZG9jdW1lbnRz\nKSwKCQlvciA8YSBocmVmPSJodHRwOi8vd3d3LmljYW5uLm9yZy9lbi9yZWdp\nc3RyaWVzL2FncmVlbWVudHMuaHRtIj5jb250cmFjdHVhbCBsaW1pdGF0aW9u\nczwvYT4uPC9wPgoJCQoJCTxwPkRvbWFpbnMgd2hpY2ggYXJlIGRlc2NyaWJl\nZCBhcyByZWdpc3RlcmVkIHRvIElBTkEgb3IgSUNBTk4gb24gcG9saWN5CgkJ\nZ3JvdW5kcyBhcmUgbm90IGF2YWlsYWJsZSBmb3IgcmVnaXN0cmF0aW9uIG9y\nIHRyYW5zZmVyLCB3aXRoIHRoZSBleGNlcHRpb24KCQlvZiA8c3BhbiBjbGFz\ncz0iZG9tYWluIGxhYmVsIj48aT5jb3VudHJ5LW5hbWU8L2k+LmluZm88L3Nw\nYW4+IGRvbWFpbnMuIFRoZXNlIGRvbWFpbnMgYXJlIGF2YWlsYWJsZSBmb3Ig\ncmVsZWFzZQoJCWJ5IHRoZSBJQ0FOTiBHb3Zlcm5tZW50YWwgQWR2aXNvcnkg\nQ29tbWl0dGVlIFNlY3JldGFyaWF0LjwvcD4KCiAgICA8aDI+T3RoZXIgU3Bl\nY2lhbC1Vc2UgRG9tYWluczwvaDI+CgogICAgPHA+VGhlcmUgaXMgYWRkaXRp\nb25hbGx5IGEgPGEgaHJlZj0iL2Fzc2lnbm1lbnRzL3NwZWNpYWwtdXNlLWRv\nbWFpbi1uYW1lcyI+U3BlY2lhbC1Vc2UgRG9tYWluIE5hbWVzPC9hPiByZWdp\nc3RyeSBkb2N1bWVudGluZyBzcGVjaWFsLXVzZSBkb21haW5zIGRlc2lnbmF0\nZWQgYnkgdGVjaG5pY2FsIHN0YW5kYXJkcy4gRm9yIGZ1cnRoZXIgaW5mb3Jt\nYXRpb24sIHNlZSA8YSBocmVmPSIvZ28vcmZjNjc2MSI+U3BlY2lhbC1Vc2Ug\nRG9tYWluIE5hbWVzPC9hPiAoUkZDIDY3NjEpLjwvcD4KCQoKCQkJPC9kaXY+\nCgkJCQoJCQk8ZGl2IGlkPSJzaWRlYmFyX2xlZnQiPgoJCQkJPGRpdiBjbGFz\ncz0ibmF2aWdhdGlvbl9ib3giPgoJCQkJPGgyPkRvbWFpbiBOYW1lczwvaDI+\nCgkJCQk8dWw+CgkJCQkJPGxpIGlkPSJuYXZfZG9tX3RvcCI+PGEgaHJlZj0i\nL2RvbWFpbnMiPk92ZXJ2aWV3PC9hPjwvbGk+CgkJCQkJPGxpIGlkPSJuYXZf\nZG9tX3Jvb3QiPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QiPlJvb3QgWm9uZSBN\nYW5hZ2VtZW50PC9hPjwvbGk+CgkJCQkJPHVsIGlkPSJuYXZfZG9tX3Jvb3Rf\nc3ViIj4KCQkJCQkJPGxpIGlkPSJuYXZfZG9tX3Jvb3RfdG9wIj48YSBocmVm\nPSIvZG9tYWlucy9yb290Ij5PdmVydmlldzwvYT48L2xpPgoJCQkJCQk8bGkg\naWQ9Im5hdl9kb21fcm9vdF9kYiI+PGEgaHJlZj0iL2RvbWFpbnMvcm9vdC9k\nYiI+Um9vdCBEYXRhYmFzZTwvYT48L2xpPgoJCQkJCQk8bGkgaWQ9Im5hdl9k\nb21fcm9vdF9maWxlcyI+PGEgaHJlZj0iL2RvbWFpbnMvcm9vdC9maWxlcyI+\nSGludCBhbmQgWm9uZSBGaWxlczwvYT48L2xpPgoJCQkJCQk8bGkgaWQ9Im5h\ndl9kb21fcm9vdF9tYW5hZ2UiPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QvbWFu\nYWdlIj5DaGFuZ2UgUmVxdWVzdHM8L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJu\nYXZfZG9tX3Jvb3RfcHJvY2VkdXJlcyI+PGEgaHJlZj0iL2RvbWFpbnMvcm9v\ndC9oZWxwIj5JbnN0cnVjdGlvbnMgJmFtcDsgR3VpZGVzPC9hPjwvbGk+CgkJ\nCQkJCTxsaSBpZD0ibmF2X2RvbV9yb290X3NlcnZlcnMiPjxhIGhyZWY9Ii9k\nb21haW5zL3Jvb3Qvc2VydmVycyI+Um9vdCBTZXJ2ZXJzPC9hPjwvbGk+CgkJ\nCQkJPC91bD4KCQkJCQk8bGkgaWQ9Im5hdl9kb21faW50Ij48YSBocmVmPSIv\nZG9tYWlucy9pbnQiPi5JTlQgUmVnaXN0cnk8L2E+PC9saT4KCQkJCQk8dWwg\naWQ9Im5hdl9kb21faW50X3N1YiI+CgkJCQkJCTxsaSBpZD0ibmF2X2RvbV9p\nbnRfdG9wIj48YSBocmVmPSIvZG9tYWlucy9pbnQiPk92ZXJ2aWV3PC9hPjwv\nbGk+CgkJCQkJCTxsaSBpZD0ibmF2X2RvbV9pbnRfbWFuYWdlIj48YSBocmVm\nPSIvZG9tYWlucy9pbnQvbWFuYWdlIj5SZWdpc3Rlci9tb2RpZnkgYW4gLklO\nVCBkb21haW48L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJuYXZfZG9tX2ludF9w\nb2xpY3kiPjxhIGhyZWY9Ii9kb21haW5zL2ludC9wb2xpY3kiPkVsaWdpYmls\naXR5PC9hPjwvbGk+CgkJCQkJPC91bD4KCQkJCQk8bGkgaWQ9Im5hdl9kb21f\nYXJwYSI+PGEgaHJlZj0iL2RvbWFpbnMvYXJwYSI+LkFSUEEgUmVnaXN0cnk8\nL2E+PC9saT4KCQkJCQk8bGkgaWQ9Im5hdl9kb21faWRuIj48YSBocmVmPSIv\nZG9tYWlucy9pZG4tdGFibGVzIj5JRE4gUHJhY3RpY2VzIFJlcG9zaXRvcnk8\nL2E+PC9saT4KCQkJCQk8dWwgaWQ9Im5hdl9kb21faWRuX3N1YiI+CgkJCQkJ\nCTxsaSBpZD0ibmF2X2RvbV9pZG5fdG9wIj48YSBocmVmPSIvZG9tYWlucy9p\nZG4tdGFibGVzIj5PdmVydmlldzwvYT48L2xpPgoJCQkJCQk8IS0tIDxsaSBp\nZD0ibmF2X2RvbV9pZG5fdGFibGVzIj48YSBocmVmPSIvZG9tYWlucy9pZG4t\ndGFibGVzL2RiIj5UYWJsZXM8L2E+PC9saT4gLS0+CgkJCQkJCTxsaSBpZD0i\nbmF2X2RvbV9pZG5fc3VibWl0Ij48YSBocmVmPSIvcHJvY2VkdXJlcy9pZG4t\ncmVwb3NpdG9yeS5odG1sIj5TdWJtaXQgYSB0YWJsZTwvYT48L2xpPgoJCQkJ\nCTwvdWw+CgkJCQkJPGxpIGlkPSJuYXZfZG9tX2Ruc3NlYyI+PGEgaHJlZj0i\nL2Ruc3NlYyI+Um9vdCBLZXkgU2lnbmluZyBLZXkgKEROU1NFQyk8L2E+PC9s\naT4KCQkJCQk8dWwgaWQ9Im5hdl9kb21fZG5zc2VjX3N1YiI+CgkJCQkJCTxs\naSBpZD0ibmF2X2RvbV9kbnNzZWNfdG9wIj48YSBocmVmPSIvZG5zc2VjIj5P\ndmVydmlldzwvYT48L2xpPgoJCQkJCQk8bGkgaWQ9Im5hdl9kb21fZG5zc2Vj\nX2tzayI+PGEgaHJlZj0iL2Ruc3NlYy9maWxlcyI+VHJ1c3RzIEFuY2hvcnMg\nYW5kIEtleXM8L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJuYXZfZG9tX2Ruc3Nl\nY19jZXJlbW9uaWVzIj48YSBocmVmPSIvZG5zc2VjL2NlcmVtb25pZXMiPlJv\nb3QgS1NLIENlcmVtb25pZXM8L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJuYXZf\nZG9tX2Ruc3NlY19kcHMiPjxhIGhyZWY9Ii9kbnNzZWMvZHBzIj5QcmFjdGlj\nZSBTdGF0ZW1lbnQ8L2E+PC9saT4KICAgICAgICAgICAgICAgICAgICAgICAg\nPGxpIGlkPSJuYXZfZG9tX2Ruc3NlY190Y3JzIj48YSBocmVmPSIvZG5zc2Vj\nL3RjcnMiPkNvbW11bml0eSBSZXByZXNlbnRhdGl2ZXM8L2E+PC9saT4KCQkJ\nCQk8L3VsPgoJCQkJCTxsaSBpZD0ibmF2X2RvbV9zcGVjaWFsIj48YSBocmVm\nPSIvZG9tYWlucy9yZXNlcnZlZCI+UmVzZXJ2ZWQgRG9tYWluczwvYT48L2xp\nPgoJCQkJPC91bD4KCQkJCTwvZGl2PgoJCQk8L2Rpdj4KCQkJCgoJPC9kaXY+\nCgoJPGZvb3Rlcj4KCQk8ZGl2IGlkPSJmb290ZXIiPgoJCQk8dGFibGUgY2xh\nc3M9Im5hdmlnYXRpb24iPgoJCQkJPHRyPgoJCQkJCTx0ZCBjbGFzcz0ic2Vj\ndGlvbiI+PGEgaHJlZj0iL2RvbWFpbnMiPkRvbWFpbiZuYnNwO05hbWVzPC9h\nPjwvdGQ+CgkJCQkJPHRkIGNsYXNzPSJzdWJzZWN0aW9uIj4KCQkJCQkJPHVs\nPgoJCQkJCQkJPGxpPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QiPlJvb3QgWm9u\nZSBSZWdpc3RyeTwvYT48L2xpPgoJCQkJCQkJPGxpPjxhIGhyZWY9Ii9kb21h\naW5zL2ludCI+LklOVCBSZWdpc3RyeTwvYT48L2xpPgoJCQkJCQkJPGxpPjxh\nIGhyZWY9Ii9kb21haW5zL2FycGEiPi5BUlBBIFJlZ2lzdHJ5PC9hPjwvbGk+\nCgkJCQkJCQk8bGk+PGEgaHJlZj0iL2RvbWFpbnMvaWRuLXRhYmxlcyI+SURO\nIFJlcG9zaXRvcnk8L2E+PC9saT4KCQkJCQkJPC91bD4KCQkJCQk8L3RkPgoJ\nCQkJPC90cj4KCQkJCTx0cj4KCQkJCQk8dGQgY2xhc3M9InNlY3Rpb24iPjxh\nIGhyZWY9Ii9udW1iZXJzIj5OdW1iZXImbmJzcDtSZXNvdXJjZXM8L2E+PC90\nZD4KCQkJCQk8dGQgY2xhc3M9InN1YnNlY3Rpb24iPgoJCQkJCQk8dWw+CgkJ\nCQkJCQk8bGk+PGEgaHJlZj0iL2FidXNlIj5BYnVzZSBJbmZvcm1hdGlvbjwv\nYT48L2xpPgoJCQkJCQk8L3VsPgoJCQkJCTwvdGQ+CgkJCQk8L3RyPgoJCQkJ\nPHRyPgoJCQkJCTx0ZCBjbGFzcz0ic2VjdGlvbiI+PGEgaHJlZj0iL3Byb3Rv\nY29scyI+UHJvdG9jb2xzPC9hPjwvdGQ+CgkJCQkJPHRkIGNsYXNzPSJzdWJz\nZWN0aW9uIj4KCQkJCQkJPHVsPgoJCQkJCQkJPGxpPjxhIGhyZWY9Ii9wcm90\nb2NvbHMiPlByb3RvY29sIFJlZ2lzdHJpZXM8L2E+PC9saT4KCQkJCQkJCTxs\naT48YSBocmVmPSIvdGltZS16b25lcyI+VGltZSBab25lIERhdGFiYXNlPC9h\nPjwvbGk+CgkJCQkJCTwvdWw+CgkJCQkJPC90ZD4KCQkJCTwvdHI+CgkJCQk8\ndHI+CgkJCQkJPHRkIGNsYXNzPSJzZWN0aW9uIj48YSBocmVmPSIvYWJvdXQi\nPkFib3V0Jm5ic3A7VXM8L2E+PC90ZD4KCQkJCQk8dGQgY2xhc3M9InN1YnNl\nY3Rpb24iPgoJCQkJCQk8dWw+CgkJCQkJCQk8bGk+PGEgaHJlZj0iL2Fib3V0\nL3ByZXNlbnRhdGlvbnMiPlByZXNlbnRhdGlvbnM8L2E+PC9saT4KCQkJCQkJ\nCTxsaT48YSBocmVmPSIvcmVwb3J0cyI+UmVwb3J0czwvYT48L2xpPgogICAg\nICAgICAgICAgICAgICAgICAgICAgICAgPGxpPjxhIGhyZWY9Ii9wZXJmb3Jt\nYW5jZSI+UGVyZm9ybWFuY2U8L2E+PC9saT4KCQkJCQkJCTxsaT48YSBocmVm\nPSIvcmV2aWV3cyI+UmV2aWV3czwvYT48L2xpPgogICAgICAgICAgICAgICAg\nICAgICAgICAgICAgPGxpPjxhIGhyZWY9Ii9hYm91dC9leGNlbGxlbmNlIj5F\neGNlbGxlbmNlPC9hPjwvbGk+CgkJCQkJCQk8bGk+PGEgaHJlZj0iL2NvbnRh\nY3QiPkNvbnRhY3QgVXM8L2E+PC9saT4KCQkJCQkJPC91bD4KCQkJCQk8L3Rk\nPgoJCQkJPC90cj4KCQkJPC90YWJsZT4KCiAgICAgICAgICAgIDxkaXYgaWQ9\nImN1c3RvZGlhbiI+CiAgICAgICAgICAgICAgICA8cD5UaGUgSUFOQSBmdW5j\ndGlvbnMgY29vcmRpbmF0ZSB0aGUgSW50ZXJuZXTigJlzIGdsb2JhbGx5IHVu\naXF1ZSBpZGVudGlmaWVycywgYW5kCiAgICAgICAgICAgICAgICAgICAgYXJl\nIHByb3ZpZGVkIGJ5IDxhIGhyZWY9Imh0dHA6Ly9wdGkuaWNhbm4ub3JnIj5Q\ndWJsaWMgVGVjaG5pY2FsIElkZW50aWZpZXJzPC9hPiwgYW4gYWZmaWxpYXRl\nIG9mCiAgICAgICAgICAgICAgICAgICAgPGEgaHJlZj0iaHR0cDovL3d3dy5p\nY2Fubi5vcmcvIj5JQ0FOTjwvYT4uPC9wPgogICAgICAgICAgICA8L2Rpdj4K\nCgkJPC9kaXY+Cgk8L2Zvb3Rlcj4KCQoJCjxzY3JpcHQ+CiQoZG9jdW1lbnQp\nLnJlYWR5KGZ1bmN0aW9uKCkgewoJJCgiI25hdl9kb21fc3BlY2lhbCIpLmFk\nZENsYXNzKCJzZWxlY3RlZCIpCgkkKCIjbmF2X2RvbV9pbnRfc3ViIikuaGlk\nZSgpCgkkKCIjbmF2X2RvbV9pZG5fc3ViIikuaGlkZSgpCgkkKCIjbmF2X2Rv\nbV9kbnNzZWNfc3ViIikuaGlkZSgpCgkkKCIjbmF2X2RvbV90b29sc19zdWIi\nKS5oaWRlKCkKCSQoIiNuYXZfZG9tX3Jvb3Rfc3ViIikuaGlkZSgpCn0pOwo8\nL3NjcmlwdD4KCgkKPC9ib2R5PgoKPC9odG1sPgo=\n" 75 | }, 76 | "http_version": null 77 | }, 78 | "recorded_at": "Wed, 06 Sep 2017 10:46:04 GMT" 79 | } 80 | ], 81 | "recorded_with": "VCR 3.0.3" 82 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | skip_tags: true 8 | 9 | build_script: 10 | - cmd: dotnet restore 11 | - cmd: dotnet build -c Release 12 | 13 | test_script: 14 | - dotnet test vcr-sharp-tests/VCRSharp.Tests.csproj -c Release 15 | 16 | after_test: 17 | - dotnet run -p VCRSharp.Benchmarks 18 | 19 | artifacts: 20 | - path: BenchmarkDotNet.Artifacts\**\* 21 | -------------------------------------------------------------------------------- /vcr-sharp-tests/CacheResult.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace VcrSharp.Tests 4 | { 5 | public class CacheResult 6 | { 7 | public bool Found { get; set; } 8 | 9 | public HttpResponseMessage Response { get; set; } 10 | 11 | public static CacheResult Success(HttpResponseMessage response) 12 | { 13 | return new CacheResult 14 | { 15 | Found = true, 16 | Response = response 17 | }; 18 | } 19 | 20 | public static CacheResult Missing() 21 | { 22 | return new CacheResult 23 | { 24 | Found = false 25 | }; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vcr-sharp-tests/Cassette.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | 9 | namespace VcrSharp.Tests 10 | { 11 | public class Cassette 12 | { 13 | private int currentIndex = 0; 14 | private readonly string cassettePath; 15 | private List cachedEntries; 16 | private List storedEntries; 17 | 18 | public Cassette(string cassettePath) 19 | { 20 | this.cassettePath = cassettePath; 21 | } 22 | 23 | async Task SetupCache() 24 | { 25 | if (File.Exists(cassettePath)) 26 | { 27 | var task = Task.Factory.StartNew(() => JsonConvert.DeserializeObject(File.ReadAllText(cassettePath))); 28 | var contents = await task; 29 | cachedEntries = new List(contents.HttpInteractions ?? Array.Empty()); 30 | } 31 | else 32 | { 33 | cachedEntries = new List(); 34 | } 35 | 36 | storedEntries = new List(); 37 | } 38 | 39 | static bool MatchesRequest(CachedRequestResponse cached, HttpRequestMessage request) 40 | { 41 | var method = request.Method.Method; 42 | var uri = request.RequestUri; 43 | 44 | return string.Equals(cached.Request.Method, method, StringComparison.OrdinalIgnoreCase) 45 | && cached.Request.Uri == uri; 46 | } 47 | 48 | internal async Task FindCachedResponseAsync(HttpRequestMessage request) 49 | { 50 | if (cachedEntries == null) 51 | { 52 | await SetupCache(); 53 | } 54 | 55 | if (currentIndex < 0 || currentIndex >= cachedEntries.Count) 56 | { 57 | return CacheResult.Missing(); 58 | } 59 | 60 | var entry = cachedEntries[currentIndex]; 61 | currentIndex++; 62 | if (MatchesRequest(entry, request)) 63 | { 64 | // persist the existing cached entry to disk 65 | storedEntries.Add(entry); 66 | return CacheResult.Success(Serializer.Deserialize(entry.Response)); 67 | } 68 | 69 | return CacheResult.Missing(); 70 | } 71 | 72 | internal async Task StoreCachedResponseAsync(HttpRequestMessage request, HttpResponseMessage freshResponse) 73 | { 74 | if (cachedEntries == null) 75 | { 76 | await SetupCache(); 77 | } 78 | 79 | var cachedResponse = new CachedRequestResponse 80 | { 81 | Request = await Serializer.Serialize(request), 82 | Response = await Serializer.Serialize(freshResponse) 83 | }; 84 | 85 | storedEntries.Add(cachedResponse); 86 | } 87 | 88 | internal Task FlushToDisk() 89 | { 90 | var json = new CachedRequestResponseArray 91 | { 92 | HttpInteractions = storedEntries.ToArray() 93 | }; 94 | 95 | var text = JsonConvert.SerializeObject(json, Formatting.Indented); 96 | var directory = Path.GetDirectoryName(cassettePath); 97 | if (!Directory.Exists(directory)) 98 | { 99 | Directory.CreateDirectory(directory); 100 | } 101 | 102 | File.WriteAllText(cassettePath, text); 103 | return Task.CompletedTask; 104 | } 105 | } 106 | 107 | internal class Body 108 | { 109 | [JsonProperty("encoding")] 110 | public string Encoding { get; set; } 111 | [JsonProperty("Base64_string")] 112 | public string Base64String { get; set; } 113 | } 114 | 115 | internal class CachedRequest 116 | { 117 | [JsonProperty("method")] 118 | public string Method { get; set; } 119 | [JsonProperty("uri")] 120 | public Uri Uri { get; set; } 121 | [JsonProperty("body")] 122 | public Body Body { get; set; } 123 | [JsonProperty("headers")] 124 | public Dictionary Headers { get; set; } 125 | } 126 | 127 | internal class Status 128 | { 129 | [JsonProperty("code")] 130 | public int Code { get; set; } 131 | [JsonProperty("message")] 132 | public string Message { get; set; } 133 | } 134 | 135 | internal class CachedResponse 136 | { 137 | [JsonProperty("status")] 138 | public Status Status { get; set; } 139 | [JsonProperty("headers")] 140 | public Dictionary Headers { get; set; } 141 | [JsonProperty("body")] 142 | public Body Body { get; set; } 143 | } 144 | 145 | internal class CachedRequestResponse 146 | { 147 | [JsonProperty("request")] 148 | public CachedRequest Request { get; set; } 149 | [JsonProperty("response")] 150 | public CachedResponse Response { get; set; } 151 | } 152 | 153 | internal class CachedRequestResponseArray 154 | { 155 | [JsonProperty("http_interactions")] 156 | public CachedRequestResponse[] HttpInteractions { get; set; } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /vcr-sharp-tests/HttpClientFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Reflection; 5 | 6 | namespace VcrSharp.Tests 7 | { 8 | public class HttpClientFactory 9 | { 10 | public static string GetFixturePath(string session) 11 | { 12 | return Path.Combine(AssemblyLoadDirectory, "fixtures", session + ".json"); 13 | } 14 | 15 | static string AssemblyLoadDirectory 16 | { 17 | get 18 | { 19 | var codeBase = Assembly.GetCallingAssembly().CodeBase; 20 | UriBuilder uri = new UriBuilder(codeBase); 21 | var path = Uri.UnescapeDataString(uri.Path); 22 | return Path.GetDirectoryName(path); 23 | } 24 | } 25 | 26 | public static HttpClient WithCassette(string session) 27 | { 28 | var currentDirectory = Assembly.GetExecutingAssembly(); 29 | var testCassettePath = GetFixturePath(session); 30 | var handler = new ReplayingHandler(testCassettePath); 31 | var httpClient = new HttpClient(handler); 32 | return httpClient; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /vcr-sharp-tests/PlaybackException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VcrSharp.Tests 4 | { 5 | public class PlaybackException : Exception 6 | { 7 | public PlaybackException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /vcr-sharp-tests/ReplayingHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace VcrSharp.Tests 7 | { 8 | public enum VCRMode 9 | { 10 | /// 11 | /// Only use the local fixtures when executing network requests 12 | /// 13 | Playback, 14 | /// 15 | /// Use the cached response if found, otherwise fetch and store the result 16 | /// 17 | Cache, 18 | /// 19 | /// Avoid cached responses - use the network and store the result 20 | /// 21 | Record 22 | } 23 | 24 | public class ReplayingHandler : DelegatingHandler 25 | { 26 | private readonly Cassette cassette; 27 | 28 | public ReplayingHandler(HttpMessageHandler innerHandler, string cassettePath) : base(innerHandler) 29 | { 30 | cassette = new Cassette(cassettePath); 31 | } 32 | 33 | public ReplayingHandler(string cassettePath) : this(new HttpClientHandler(), cassettePath) 34 | { 35 | 36 | } 37 | 38 | static VCRMode Parse(string mode) 39 | { 40 | if (string.IsNullOrWhiteSpace(mode)) 41 | { 42 | return VCRMode.Playback; 43 | } 44 | 45 | var text = mode.Trim(); 46 | if (text.Equals("playback", StringComparison.OrdinalIgnoreCase)) 47 | { 48 | return VCRMode.Playback; 49 | } 50 | 51 | if (text.Equals("cache", StringComparison.OrdinalIgnoreCase)) 52 | { 53 | return VCRMode.Cache; 54 | } 55 | 56 | if (text.Equals("record", StringComparison.OrdinalIgnoreCase)) 57 | { 58 | return VCRMode.Record; 59 | } 60 | 61 | return VCRMode.Playback; 62 | } 63 | 64 | VCRMode? _vcrMode; 65 | VCRMode CurrentVCRMode 66 | { 67 | get 68 | { 69 | if (!_vcrMode.HasValue) 70 | { 71 | _vcrMode = Parse(Environment.GetEnvironmentVariable("VCR_MODE")); 72 | } 73 | return _vcrMode.Value; 74 | } 75 | } 76 | 77 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 78 | { 79 | if (CurrentVCRMode != VCRMode.Record) 80 | { 81 | var cachedResponse = await cassette.FindCachedResponseAsync(request); 82 | if (cachedResponse.Found) 83 | { 84 | return cachedResponse.Response; 85 | } 86 | } 87 | 88 | if (CurrentVCRMode == VCRMode.Playback) 89 | { 90 | throw new PlaybackException("A cached response was not found, and the environment is in playback mode which means the network cannot be accessed."); 91 | } 92 | 93 | var freshResponse = await base.SendAsync(request, cancellationToken); 94 | 95 | await cassette.StoreCachedResponseAsync(request, freshResponse); 96 | await cassette.FlushToDisk(); 97 | 98 | return freshResponse; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /vcr-sharp-tests/ScratchPad.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Newtonsoft.Json; 6 | using Shouldly; 7 | using Xunit; 8 | 9 | namespace VcrSharp.Tests 10 | { 11 | public class ScratchPad : IDisposable 12 | { 13 | [Fact] 14 | public async Task UseTheLocalFixtureToRetrieveTheResponse() 15 | { 16 | Environment.SetEnvironmentVariable("VCR_MODE", "playback"); 17 | 18 | using (var httpClient = HttpClientFactory.WithCassette("example-test")) 19 | { 20 | var request = new HttpRequestMessage(HttpMethod.Get, "http://www.iana.org/domains/reserved"); 21 | var response = await httpClient.SendAsync(request); 22 | var body = await response.Content.ReadAsStringAsync(); 23 | body.ShouldContain("Example domains"); 24 | } 25 | } 26 | 27 | [Fact] 28 | public async Task ErrorsWhenTheRequestIsNotInTheCache() 29 | { 30 | Environment.SetEnvironmentVariable("VCR_MODE", "playback"); 31 | 32 | using (var httpClient = HttpClientFactory.WithCassette("no-cache-defined")) 33 | { 34 | var request = new HttpRequestMessage(HttpMethod.Get, "http://www.iana.org/domains/reserved"); 35 | await Assert.ThrowsAsync(() => httpClient.SendAsync(request)); 36 | } 37 | } 38 | 39 | [Fact] 40 | public async Task WritesAndFlushesAResponseToDisk() 41 | { 42 | Environment.SetEnvironmentVariable("VCR_MODE", "record"); 43 | var session = "create-local-file"; 44 | 45 | using (var httpClient = HttpClientFactory.WithCassette(session)) 46 | { 47 | var request = new HttpRequestMessage(HttpMethod.Get, "http://www.iana.org/domains/reserved"); 48 | var response = await httpClient.SendAsync(request); 49 | } 50 | 51 | var cassette = await ReadCassetteFile(session); 52 | cassette.HttpInteractions.Length.ShouldBe(1); 53 | } 54 | 55 | [Fact] 56 | public async Task ReplacesExistingRequest() 57 | { 58 | Environment.SetEnvironmentVariable("VCR_MODE", "cache"); 59 | var session = "overwrite-request"; 60 | 61 | var cassette = await ReadCassetteFile(session); 62 | cassette.HttpInteractions.Length.ShouldBe(1); 63 | 64 | using (var httpClient = HttpClientFactory.WithCassette(session)) 65 | { 66 | var request = new HttpRequestMessage(HttpMethod.Get, "https://www.iana.org/performance/ietf-statistics"); 67 | var response = await httpClient.SendAsync(request); 68 | } 69 | 70 | cassette = await ReadCassetteFile(session); 71 | cassette.HttpInteractions.Length.ShouldBe(1); 72 | } 73 | 74 | static async Task ReadCassetteFile(string session) 75 | { 76 | var file = HttpClientFactory.GetFixturePath(session); 77 | File.Exists(file).ShouldBe(true); 78 | var text = await File.ReadAllTextAsync(file); 79 | return JsonConvert.DeserializeObject(text); 80 | } 81 | 82 | private bool disposedValue = false; 83 | protected virtual void Dispose(bool disposing) 84 | { 85 | if (!disposedValue) 86 | { 87 | if (disposing) 88 | { 89 | Environment.SetEnvironmentVariable("VCR_MODE", ""); 90 | } 91 | 92 | disposedValue = true; 93 | } 94 | } 95 | 96 | public void Dispose() 97 | { 98 | Dispose(true); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /vcr-sharp-tests/Serializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net.Http; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace VcrSharp.Tests 8 | { 9 | public class Serializer 10 | { 11 | internal static async Task Serialize(HttpRequestMessage request) 12 | { 13 | return new CachedRequest 14 | { 15 | Headers = request.Headers.ToDictionary(h => h.Key, h => h.Value.ToArray()), 16 | Method = request.Method.Method, 17 | Uri = request.RequestUri, 18 | Body = await ParseContent(request.Content) 19 | }; 20 | } 21 | 22 | internal static HttpRequestMessage Deserialize(CachedRequest cachedRequest) 23 | { 24 | var request = new HttpRequestMessage(); 25 | request.Method = new HttpMethod(cachedRequest.Method); 26 | request.RequestUri = cachedRequest.Uri; 27 | foreach (var kvp in cachedRequest.Headers) 28 | { 29 | if (IsValidHeader(kvp.Key)) 30 | { 31 | request.Headers.Add(kvp.Key, kvp.Value); 32 | } 33 | } 34 | 35 | request.Content = SetContent(cachedRequest.Body, ""); 36 | return request; 37 | } 38 | 39 | internal static async Task Serialize(HttpResponseMessage freshResponse) 40 | { 41 | var responseHeaders = freshResponse.Headers.ToDictionary(h => h.Key, h => h.Value.ToArray()); 42 | foreach (var kvp in freshResponse.Content.Headers) 43 | { 44 | responseHeaders.Add(kvp.Key, kvp.Value.ToArray()); 45 | } 46 | 47 | return new CachedResponse 48 | { 49 | Headers = responseHeaders, 50 | Status = new Status 51 | { 52 | Code = (int)freshResponse.StatusCode, 53 | Message = freshResponse.StatusCode.ToString() 54 | }, 55 | Body = await ParseContent(freshResponse.Content) 56 | }; 57 | } 58 | 59 | internal static HttpResponseMessage Deserialize(CachedResponse cachedResponse) 60 | { 61 | var statusCode = (System.Net.HttpStatusCode)cachedResponse.Status.Code; 62 | var response = new HttpResponseMessage(statusCode); 63 | foreach (var kvp in cachedResponse.Headers) 64 | { 65 | if (IsValidHeader(kvp.Key)) 66 | { 67 | response.Headers.Add(kvp.Key, kvp.Value); 68 | } 69 | } 70 | 71 | if (cachedResponse.Headers.TryGetValue("Content-Type", out var values)) 72 | { 73 | // TODO: we have encoding information here, we should use that instead of assuming UTF-8 74 | var value = values.ElementAt(0); 75 | var array = value.Split(';'); 76 | var rawText = array[0]; 77 | response.Content = SetContent(cachedResponse.Body, rawText); 78 | } 79 | return response; 80 | } 81 | 82 | static async Task ParseContent(HttpContent content) 83 | { 84 | if (content == null) 85 | { 86 | return new Body 87 | { 88 | Encoding = "", 89 | Base64String = "" 90 | }; 91 | } 92 | 93 | var text = await content.ReadAsStringAsync(); 94 | var bytes = Encoding.UTF8.GetBytes(text); 95 | return new Body 96 | { 97 | Base64String = Convert.ToBase64String(bytes), 98 | Encoding = "UTF8-8BIT" 99 | }; 100 | } 101 | 102 | static HttpContent SetContent(Body body, string mediaType) 103 | { 104 | if (body.Encoding == "ASCII-8BIT") 105 | { 106 | var text = body.Base64String; 107 | var textWithoutNewLines = text.Replace("\n", ""); 108 | var decodedBytes = Convert.FromBase64String(textWithoutNewLines); 109 | var output = Encoding.UTF8.GetString(decodedBytes); 110 | return new StringContent(output); 111 | } 112 | 113 | if (body.Encoding == "UTF8-8BIT") 114 | { 115 | var text = body.Base64String; 116 | var decodedBytes = Convert.FromBase64String(text); 117 | var output = Encoding.UTF8.GetString(decodedBytes); 118 | return new StringContent(output, Encoding.UTF8, mediaType); 119 | } 120 | 121 | return null; 122 | } 123 | 124 | static bool IsValidHeader(string header) 125 | { 126 | if (header == "Content-Length" 127 | || header == "Last-Modified" 128 | || header == "Expires" 129 | || header == "Content-Type") 130 | { 131 | return false; 132 | } 133 | 134 | return true; 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /vcr-sharp-tests/SerializerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | using VcrSharp.Tests; 4 | using Xunit; 5 | 6 | public class SerializerTests 7 | { 8 | public class Get 9 | { 10 | [Fact] 11 | public async Task RountTripRequest() 12 | { 13 | var client = new HttpClient(); 14 | var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/meta"); 15 | request.Headers.UserAgent.ParseAdd("vcr-test"); 16 | var response = await client.SendAsync(request); 17 | 18 | var serializedRequest = await Serializer.Serialize(request); 19 | var deserializedRequest = Serializer.Deserialize(serializedRequest); 20 | 21 | Assert.Equal(request.Version, deserializedRequest.Version); 22 | Assert.Equal(request.Method, deserializedRequest.Method); 23 | Assert.Equal(request.Content, deserializedRequest.Content); 24 | } 25 | 26 | [Fact] 27 | public async Task RountTripResponse() 28 | { 29 | var client = new HttpClient(); 30 | var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/meta"); 31 | request.Headers.UserAgent.ParseAdd("vcr-test"); 32 | var response = await client.SendAsync(request); 33 | 34 | var serializedResponse = await Serializer.Serialize(response); 35 | var deserializedResponse = Serializer.Deserialize(serializedResponse); 36 | 37 | Assert.Equal(deserializedResponse.Version, response.Version); 38 | Assert.Equal(deserializedResponse.StatusCode, response.StatusCode); 39 | Assert.Equal(deserializedResponse.Content.Headers.ContentType, response.Content.Headers.ContentType); 40 | 41 | var actualText = await deserializedResponse.Content.ReadAsStringAsync(); 42 | var expectedText = await response.Content.ReadAsStringAsync(); 43 | 44 | Assert.Equal(actualText, expectedText); 45 | } 46 | } 47 | 48 | public class Post 49 | { 50 | [Fact] 51 | public async Task RountTripRequest() 52 | { 53 | var client = new HttpClient(); 54 | var request = new HttpRequestMessage(HttpMethod.Post, "https://api.github.com/user/repos"); 55 | request.Headers.UserAgent.ParseAdd("vcr-test"); 56 | var response = await client.SendAsync(request); 57 | 58 | var serializedRequest = await Serializer.Serialize(request); 59 | var deserializedRequest = Serializer.Deserialize(serializedRequest); 60 | 61 | Assert.Equal(request.Version, deserializedRequest.Version); 62 | Assert.Equal(request.Method, deserializedRequest.Method); 63 | Assert.Equal(request.Content, deserializedRequest.Content); 64 | } 65 | 66 | [Fact] 67 | public async Task RountTripResponse() 68 | { 69 | var client = new HttpClient(); 70 | var request = new HttpRequestMessage(HttpMethod.Post, "https://api.github.com/user/repos"); 71 | request.Headers.UserAgent.ParseAdd("vcr-test"); 72 | var response = await client.SendAsync(request); 73 | 74 | var serializedResponse = await Serializer.Serialize(response); 75 | var deserializedResponse = Serializer.Deserialize(serializedResponse); 76 | 77 | Assert.Equal(response.Version, deserializedResponse.Version); 78 | Assert.Equal(response.StatusCode, deserializedResponse.StatusCode); 79 | Assert.Equal(response.Content.Headers.ContentType, deserializedResponse.Content.Headers.ContentType); 80 | 81 | var actualText = await deserializedResponse.Content.ReadAsStringAsync(); 82 | var expectedText = await response.Content.ReadAsStringAsync(); 83 | 84 | Assert.Equal(actualText, expectedText); 85 | } 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /vcr-sharp-tests/VCRSharp.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.0 4 | false 5 | VcrSharp.Tests 6 | VcrSharp.Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Always 19 | 20 | 21 | Always 22 | 23 | 24 | -------------------------------------------------------------------------------- /vcr-sharp-tests/fixtures/example-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "http_interactions": [ 3 | { 4 | "request": { 5 | "method": "get", 6 | "uri": "http://www.iana.org/domains/reserved", 7 | "body": { 8 | "encoding": "US-ASCII", 9 | "base64_string": "" 10 | }, 11 | "headers": { 12 | "Accept-Encoding": [ 13 | "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" 14 | ], 15 | "Accept": [ 16 | "*/*" 17 | ], 18 | "User-Agent": [ 19 | "Ruby" 20 | ], 21 | "Host": [ 22 | "www.iana.org" 23 | ] 24 | } 25 | }, 26 | "response": { 27 | "status": { 28 | "code": 200, 29 | "message": "OK" 30 | }, 31 | "headers": { 32 | "Date": [ 33 | "Wed, 06 Sep 2017 10:32:27 GMT" 34 | ], 35 | "Content-Length": [ 36 | "3093" 37 | ], 38 | "Vary": [ 39 | "Accept-Encoding" 40 | ], 41 | "Last-Modified": [ 42 | "Tue, 21 Jul 2015 00:49:48 GMT" 43 | ], 44 | "Expires": [ 45 | "Wed, 06 Sep 2017 12:30:59 GMT" 46 | ], 47 | "X-Frame-Options": [ 48 | "SAMEORIGIN" 49 | ], 50 | "Content-Security-Policy": [ 51 | "upgrade-insecure-requests" 52 | ], 53 | "Content-Type": [ 54 | "text/html; charset=UTF-8" 55 | ], 56 | "Server": [ 57 | "Apache" 58 | ], 59 | "Strict-Transport-Security": [ 60 | "max-age=47304003; preload" 61 | ], 62 | "X-Cache-Hits": [ 63 | "26" 64 | ], 65 | "Accept-Ranges": [ 66 | "bytes" 67 | ], 68 | "Connection": [ 69 | "keep-alive" 70 | ] 71 | }, 72 | "body": { 73 | "encoding": "ASCII-8BIT", 74 | "base64_string": "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgo8aGVhZD4KCTx0aXRsZT5JQU5BIOKA\nlCBJQU5BLW1hbmFnZWQgUmVzZXJ2ZWQgRG9tYWluczwvdGl0bGU+CgoJPG1l\ndGEgY2hhcnNldD0idXRmLTgiIC8+Cgk8bWV0YSBodHRwLWVxdWl2PSJDb250\nZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIg\nLz4KCTxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZp\nY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSIgLz4KCQoJPGxpbmsgcmVsPSJz\ndHlsZXNoZWV0IiBtZWRpYT0ic2NyZWVuIiBocmVmPSIvX2Nzcy8yMDE1LjEv\nc2NyZWVuLmNzcyIvPgoJPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBtZWRpYT0i\ncHJpbnQiIGhyZWY9Ii9fY3NzLzIwMTUuMS9wcmludC5jc3MiLz4KCTxsaW5r\nIHJlbD0ic2hvcnRjdXQgaWNvbiIgdHlwZT0iaW1hZ2UvaWNvIiBocmVmPSIv\nX2ltZy9ib29rbWFya19pY29uLmljbyIvPgoKCTxzY3JpcHQgdHlwZT0idGV4\ndC9qYXZhc2NyaXB0IiBzcmM9Ii9fanMvMjAxMy4xL2pxdWVyeS5qcyI+PC9z\nY3JpcHQ+Cgk8c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCIgc3JjPSIv\nX2pzLzIwMTMuMS9pYW5hLmpzIj48L3NjcmlwdD4KCgkKCjwvaGVhZD4KCjxi\nb2R5PgoJCgk8aGVhZGVyPgoJCTxkaXYgaWQ9ImhlYWRlciI+CgkJCTxkaXYg\naWQ9ImxvZ28iPgoJCQkJPGEgaHJlZj0iLyI+PGltZyBzcmM9Ii9faW1nLzIw\nMTMuMS9pYW5hLWxvZ28taGVhZGVyLnN2ZyIgYWx0PSJIb21lcGFnZSIvPjwv\nYT4KCQkJPC9kaXY+CgkJCTxkaXYgY2xhc3M9Im5hdmlnYXRpb24iPgoJCQkJ\nPHVsPgoJCQkJCTxsaT48YSBocmVmPSIvZG9tYWlucyI+RG9tYWluczwvYT48\nL2xpPgoJCQkJCTxsaT48YSBocmVmPSIvbnVtYmVycyI+TnVtYmVyczwvYT48\nL2xpPgoJCQkJCTxsaT48YSBocmVmPSIvcHJvdG9jb2xzIj5Qcm90b2NvbHM8\nL2E+PC9saT4KCQkJCQk8bGk+PGEgaHJlZj0iL2Fib3V0Ij5BYm91dCBVczwv\nYT48L2xpPgoJCQkJPC91bD4KCQkJPC9kaXY+CgkJPC9kaXY+Cgk8L2hlYWRl\ncj4KCQoJPGRpdiBpZD0iYm9keSI+CgkKCgkJCTxkaXYgaWQ9Im1haW5fcmln\naHQiPgoKCgk8aDE+SUFOQS1tYW5hZ2VkIFJlc2VydmVkIERvbWFpbnM8L2gx\nPgoKCTxwPkNlcnRhaW4gZG9tYWlucyBhcmUgc2V0IGFzaWRlLCBhbmQgbm9t\naW5hbGx5IHJlZ2lzdGVyZWQgdG8gJmxkcXVvO0lBTkEmcmRxdW87LCBmb3Ig\nc3BlY2lmaWMKCQlwb2xpY3kgb3IgdGVjaG5pY2FsIHB1cnBvc2VzLjwvcD4K\nCgk8aDI+RXhhbXBsZSBkb21haW5zPC9oMj4KCQoJPHA+QXMgZGVzY3JpYmVk\nIGluIDxhIGhyZWY9Ii9nby9yZmMyNjA2Ij5SRkMgMjYwNjwvYT4gYW5kIDxh\nIGhyZWY9Ii9nby9yZmM2NzYxIj5SRkMgNjc2MTwvYT4sCglhIG51bWJlciBv\nZiBkb21haW5zIHN1Y2ggYXMgPHNwYW4gY2xhc3M9ImRvbWFpbiBsYWJlbCI+\nZXhhbXBsZS5jb208L3NwYW4+IGFuZCA8c3BhbiBjbGFzcz0iZG9tYWluIGxh\nYmVsIj5leGFtcGxlLm9yZzwvc3Bhbj4KCWFyZSBtYWludGFpbmVkIGZvciBk\nb2N1bWVudGF0aW9uIHB1cnBvc2VzLiBUaGVzZSBkb21haW5zIG1heSBiZSB1\nc2VkIGFzIGlsbHVzdHJhdGl2ZQoJZXhhbXBsZXMgaW4gZG9jdW1lbnRzIHdp\ndGhvdXQgcHJpb3IgY29vcmRpbmF0aW9uIHdpdGggdXMuIFRoZXkgYXJlIAoJ\nbm90IGF2YWlsYWJsZSBmb3IgcmVnaXN0cmF0aW9uIG9yIHRyYW5zZmVyLjwv\ncD4KCgk8aDI+VGVzdCBJRE4gdG9wLWxldmVsIGRvbWFpbnM8L2gyPgoKCSAg\nICAgICAgPHA+VGhlc2UgZG9tYWlucyB3ZXJlIHRlbXBvcmFyaWx5IGRlbGVn\nYXRlZCBieSBJQU5BIGZvciB0aGUgPGEgaHJlZj0iaHR0cDovL3d3dy5pY2Fu\nbi5vcmcvdG9waWNzL2lkbi8iPklETiBFdmFsdWF0aW9uPC9hPiBiZWluZyBj\nb25kdWN0ZWQgYnkgPGEgaHJlZj0iaHR0cDovL3d3dy5pY2Fubi5vcmcvIj5J\nQ0FOTjwvYT4uPC9wPgoKCQk8ZGl2IGNsYXNzPSJpYW5hLXRhYmxlLWZyYW1l\nIj4KCQk8dGFibGUgaWQ9ImFycGEtdGFibGUiIGNsYXNzPSJpYW5hLXRhYmxl\nIj4KCQkJPHRoZWFkPgoJCQk8dHI+PHRoPkRvbWFpbjwvdGg+PHRoPkRvbWFp\nbiAoQS1sYWJlbCk8L3RoPjx0aD5MYW5ndWFnZTwvdGg+PHRoPlNjcmlwdDwv\ndGg+PC90cj4KCQkJPC90aGVhZD4KCQkJPHRib2R5PgoJCQk8dHI+PHRkPiYj\nMTU3MzsmIzE1ODI7JiMxNTc4OyYjMTU3NjsmIzE1NzU7JiMxNTg1OzwvdGQ+\nPHRkPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21h\naW5zL3Jvb3QvZGIveG4tLWtnYmVjaHR2Lmh0bWwiPlhOLS1LR0JFQ0hUVjwv\nYT48L3NwYW4+PC90ZD4KCTx0ZD5BcmFiaWM8L3RkPjx0ZD5BcmFiaWM8L3Rk\nPjwvdHI+Cgk8dHI+PHRkPiYjMTU3MDsmIzE1ODY7JiMxNjA1OyYjMTU3NTsm\nIzE3NDA7JiMxNTg4OyYjMTc0MDs8L3RkPjx0ZD48c3BhbiBjbGFzcz0iZG9t\nYWluIGxhYmVsIj48YSBocmVmPSIvZG9tYWlucy9yb290L2RiL3huLS1oZ2Jr\nNmFqN2Y1M2JiYS5odG1sIj5YTi0tSEdCSzZBSjdGNTNCQkE8L2E+PC9zcGFu\nPjwvdGQ+Cgk8dGQ+UGVyc2lhbjwvdGQ+PHRkPkFyYWJpYzwvdGQ+PC90cj4K\nCTx0cj48dGQ+JiMyNzk3OTsmIzM1Nzk3OzwvdGQ+PHRkPjxzcGFuIGNsYXNz\nPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QvZGIveG4t\nLTB6d201NmQuaHRtbCI+WE4tLTBaV001NkQ8L2E+PC9zcGFuPjwvdGQ+Cgk8\ndGQ+Q2hpbmVzZTwvdGQ+PHRkPkhhbiAoU2ltcGxpZmllZCB2YXJpYW50KTwv\ndGQ+PC90cj4KCTx0cj48dGQ+JiMyODIwNDsmIzM1NDMwOzwvdGQ+PHRkPjxz\ncGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21haW5zL3Jv\nb3QvZGIveG4tLWc2dzI1MWQuaHRtbCI+WE4tLUc2VzI1MUQ8L2E+PC9zcGFu\nPjwvdGQ+Cgk8dGQ+Q2hpbmVzZTwvdGQ+PHRkPkhhbiAoVHJhZGl0aW9uYWwg\ndmFyaWFudCk8L3RkPjwvdHI+Cgk8dHI+PHRkPiYjMTA4MDsmIzEwODk7JiMx\nMDg3OyYjMTA5OTsmIzEwOTA7JiMxMDcyOyYjMTA4NTsmIzEwODA7JiMxMDc3\nOzwvdGQ+PHRkPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9\nIi9kb21haW5zL3Jvb3QvZGIveG4tLTgwYWtoYnlrbmo0Zi5odG1sIj5YTi0t\nODBBS0hCWUtOSjRGPC9hPjwvc3Bhbj48L3RkPgoJPHRkPlJ1c3NpYW48L3Rk\nPjx0ZD5DeXJpbGxpYzwvdGQ+PC90cj4KCTx0cj48dGQ+JiMyMzQ2OyYjMjM1\nMjsmIzIzNjg7JiMyMzI1OyYjMjM4MTsmIzIzNTk7JiMyMzY2OzwvdGQ+PHRk\nPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21haW5z\nL3Jvb3QvZGIveG4tLTExYjViczNhOWFqNmcuaHRtbCI+WE4tLTExQjVCUzNB\nOUFKNkc8L2E+PC9zcGFuPjwvdGQ+Cgk8dGQ+SGluZGk8L3RkPjx0ZD5EZXZh\nbmFnYXJpIChOYWdhcmkpPC90ZD48L3RyPgoJPHRyPjx0ZD4mIzk0ODsmIzk1\nOTsmIzk1NDsmIzk1MzsmIzk1NjsmIzk0Mjs8L3RkPjx0ZD48c3BhbiBjbGFz\ncz0iZG9tYWluIGxhYmVsIj48YSBocmVmPSIvZG9tYWlucy9yb290L2RiL3hu\nLS1qeGFscGRscC5odG1sIj5YTi0tSlhBTFBETFA8L2E+PC9zcGFuPjwvdGQ+\nCgk8dGQ+R3JlZWssIE1vZGVybiAoMTQ1My0pPC90ZD48dGQ+R3JlZWs8L3Rk\nPjwvdHI+Cgk8dHI+PHRkPiYjNTM1ODA7JiM0OTgyODsmIzUzOTQ0OzwvdGQ+\nPHRkPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21h\naW5zL3Jvb3QvZGIveG4tLTl0NGIxMXlpNWEuaHRtbCI+WE4tLTlUNEIxMVlJ\nNUE8L2E+PC9zcGFuPjwvdGQ+Cgk8dGQ+S29yZWFuPC90ZD48dGQ+SGFuZ3Vs\nIChIYW5nJiN4MTZEO2wsIEhhbmdldWwpPC90ZD48L3RyPgoJPHRyPjx0ZD4m\nIzE0OTY7JiMxNTA2OyYjMTUwNTsmIzE0OTY7PC90ZD48dGQ+PHNwYW4gY2xh\nc3M9ImRvbWFpbiBsYWJlbCI+PGEgaHJlZj0iL2RvbWFpbnMvcm9vdC9kYi94\nbi0tZGViYTBhZC5odG1sIj5YTi0tREVCQTBBRDwvYT48L3NwYW4+PC90ZD4K\nCTx0ZD5ZaWRkaXNoPC90ZD48dGQ+SGVicmV3PC90ZD48L3RyPgoJPHRyPjx0\nZD4mIzEyNDg2OyYjMTI0NzM7JiMxMjQ4ODs8L3RkPjx0ZD48c3BhbiBjbGFz\ncz0iZG9tYWluIGxhYmVsIj48YSBocmVmPSIvZG9tYWlucy9yb290L2RiL3hu\nLS16Y2t6YWguaHRtbCI+WE4tLVpDS1pBSDwvYT48L3NwYW4+PC90ZD4KCTx0\nZD5KYXBhbmVzZTwvdGQ+PHRkPkthdGFrYW5hPC90ZD48L3RyPgoJPHRyPjx0\nZD4mIzI5ODY7JiMyOTkyOyYjMzAwNzsmIzI5NzU7JiMzMDIxOyYjMjk3MDsm\nIzMwMTY7PC90ZD48dGQ+PHNwYW4gY2xhc3M9ImRvbWFpbiBsYWJlbCI+PGEg\naHJlZj0iL2RvbWFpbnMvcm9vdC9kYi94bi0taGxjajZheWE5ZXNjN2EuaHRt\nbCI+WE4tLUhMQ0o2QVlBOUVTQzdBPC9hPjwvc3Bhbj48L3RkPgoJPHRkPlRh\nbWlsPC90ZD48dGQ+VGFtaWw8L3RkPjwvdHI+PC90Ym9keT4KCQk8L3RhYmxl\nPgoJICAgICAgICA8L2Rpdj4KCgk8aDI+UG9saWN5LXJlc2VydmVkIGRvbWFp\nbnM8L2gyPgoJCgk8cD5XZSBhY3QgYXMgYm90aCB0aGUgcmVnaXN0cmFudCBh\nbmQgcmVnaXN0cmFyIGZvciBhIHNlbGVjdCBudW1iZXIgb2YgZG9tYWlucwoJ\nCXdoaWNoIGhhdmUgYmVlbiByZXNlcnZlZCB1bmRlciBwb2xpY3kgZ3JvdW5k\ncy4gVGhlc2UgZXhjbHVzaW9ucyBhcmUKCQl0eXBpY2FsbHkgaW5kaWNhdGVk\nIGluIGVpdGhlciB0ZWNobmljYWwgc3RhbmRhcmRzIChSRkMgZG9jdW1lbnRz\nKSwKCQlvciA8YSBocmVmPSJodHRwOi8vd3d3LmljYW5uLm9yZy9lbi9yZWdp\nc3RyaWVzL2FncmVlbWVudHMuaHRtIj5jb250cmFjdHVhbCBsaW1pdGF0aW9u\nczwvYT4uPC9wPgoJCQoJCTxwPkRvbWFpbnMgd2hpY2ggYXJlIGRlc2NyaWJl\nZCBhcyByZWdpc3RlcmVkIHRvIElBTkEgb3IgSUNBTk4gb24gcG9saWN5CgkJ\nZ3JvdW5kcyBhcmUgbm90IGF2YWlsYWJsZSBmb3IgcmVnaXN0cmF0aW9uIG9y\nIHRyYW5zZmVyLCB3aXRoIHRoZSBleGNlcHRpb24KCQlvZiA8c3BhbiBjbGFz\ncz0iZG9tYWluIGxhYmVsIj48aT5jb3VudHJ5LW5hbWU8L2k+LmluZm88L3Nw\nYW4+IGRvbWFpbnMuIFRoZXNlIGRvbWFpbnMgYXJlIGF2YWlsYWJsZSBmb3Ig\ncmVsZWFzZQoJCWJ5IHRoZSBJQ0FOTiBHb3Zlcm5tZW50YWwgQWR2aXNvcnkg\nQ29tbWl0dGVlIFNlY3JldGFyaWF0LjwvcD4KCiAgICA8aDI+T3RoZXIgU3Bl\nY2lhbC1Vc2UgRG9tYWluczwvaDI+CgogICAgPHA+VGhlcmUgaXMgYWRkaXRp\nb25hbGx5IGEgPGEgaHJlZj0iL2Fzc2lnbm1lbnRzL3NwZWNpYWwtdXNlLWRv\nbWFpbi1uYW1lcyI+U3BlY2lhbC1Vc2UgRG9tYWluIE5hbWVzPC9hPiByZWdp\nc3RyeSBkb2N1bWVudGluZyBzcGVjaWFsLXVzZSBkb21haW5zIGRlc2lnbmF0\nZWQgYnkgdGVjaG5pY2FsIHN0YW5kYXJkcy4gRm9yIGZ1cnRoZXIgaW5mb3Jt\nYXRpb24sIHNlZSA8YSBocmVmPSIvZ28vcmZjNjc2MSI+U3BlY2lhbC1Vc2Ug\nRG9tYWluIE5hbWVzPC9hPiAoUkZDIDY3NjEpLjwvcD4KCQoKCQkJPC9kaXY+\nCgkJCQoJCQk8ZGl2IGlkPSJzaWRlYmFyX2xlZnQiPgoJCQkJPGRpdiBjbGFz\ncz0ibmF2aWdhdGlvbl9ib3giPgoJCQkJPGgyPkRvbWFpbiBOYW1lczwvaDI+\nCgkJCQk8dWw+CgkJCQkJPGxpIGlkPSJuYXZfZG9tX3RvcCI+PGEgaHJlZj0i\nL2RvbWFpbnMiPk92ZXJ2aWV3PC9hPjwvbGk+CgkJCQkJPGxpIGlkPSJuYXZf\nZG9tX3Jvb3QiPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QiPlJvb3QgWm9uZSBN\nYW5hZ2VtZW50PC9hPjwvbGk+CgkJCQkJPHVsIGlkPSJuYXZfZG9tX3Jvb3Rf\nc3ViIj4KCQkJCQkJPGxpIGlkPSJuYXZfZG9tX3Jvb3RfdG9wIj48YSBocmVm\nPSIvZG9tYWlucy9yb290Ij5PdmVydmlldzwvYT48L2xpPgoJCQkJCQk8bGkg\naWQ9Im5hdl9kb21fcm9vdF9kYiI+PGEgaHJlZj0iL2RvbWFpbnMvcm9vdC9k\nYiI+Um9vdCBEYXRhYmFzZTwvYT48L2xpPgoJCQkJCQk8bGkgaWQ9Im5hdl9k\nb21fcm9vdF9maWxlcyI+PGEgaHJlZj0iL2RvbWFpbnMvcm9vdC9maWxlcyI+\nSGludCBhbmQgWm9uZSBGaWxlczwvYT48L2xpPgoJCQkJCQk8bGkgaWQ9Im5h\ndl9kb21fcm9vdF9tYW5hZ2UiPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QvbWFu\nYWdlIj5DaGFuZ2UgUmVxdWVzdHM8L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJu\nYXZfZG9tX3Jvb3RfcHJvY2VkdXJlcyI+PGEgaHJlZj0iL2RvbWFpbnMvcm9v\ndC9oZWxwIj5JbnN0cnVjdGlvbnMgJmFtcDsgR3VpZGVzPC9hPjwvbGk+CgkJ\nCQkJCTxsaSBpZD0ibmF2X2RvbV9yb290X3NlcnZlcnMiPjxhIGhyZWY9Ii9k\nb21haW5zL3Jvb3Qvc2VydmVycyI+Um9vdCBTZXJ2ZXJzPC9hPjwvbGk+CgkJ\nCQkJPC91bD4KCQkJCQk8bGkgaWQ9Im5hdl9kb21faW50Ij48YSBocmVmPSIv\nZG9tYWlucy9pbnQiPi5JTlQgUmVnaXN0cnk8L2E+PC9saT4KCQkJCQk8dWwg\naWQ9Im5hdl9kb21faW50X3N1YiI+CgkJCQkJCTxsaSBpZD0ibmF2X2RvbV9p\nbnRfdG9wIj48YSBocmVmPSIvZG9tYWlucy9pbnQiPk92ZXJ2aWV3PC9hPjwv\nbGk+CgkJCQkJCTxsaSBpZD0ibmF2X2RvbV9pbnRfbWFuYWdlIj48YSBocmVm\nPSIvZG9tYWlucy9pbnQvbWFuYWdlIj5SZWdpc3Rlci9tb2RpZnkgYW4gLklO\nVCBkb21haW48L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJuYXZfZG9tX2ludF9w\nb2xpY3kiPjxhIGhyZWY9Ii9kb21haW5zL2ludC9wb2xpY3kiPkVsaWdpYmls\naXR5PC9hPjwvbGk+CgkJCQkJPC91bD4KCQkJCQk8bGkgaWQ9Im5hdl9kb21f\nYXJwYSI+PGEgaHJlZj0iL2RvbWFpbnMvYXJwYSI+LkFSUEEgUmVnaXN0cnk8\nL2E+PC9saT4KCQkJCQk8bGkgaWQ9Im5hdl9kb21faWRuIj48YSBocmVmPSIv\nZG9tYWlucy9pZG4tdGFibGVzIj5JRE4gUHJhY3RpY2VzIFJlcG9zaXRvcnk8\nL2E+PC9saT4KCQkJCQk8dWwgaWQ9Im5hdl9kb21faWRuX3N1YiI+CgkJCQkJ\nCTxsaSBpZD0ibmF2X2RvbV9pZG5fdG9wIj48YSBocmVmPSIvZG9tYWlucy9p\nZG4tdGFibGVzIj5PdmVydmlldzwvYT48L2xpPgoJCQkJCQk8IS0tIDxsaSBp\nZD0ibmF2X2RvbV9pZG5fdGFibGVzIj48YSBocmVmPSIvZG9tYWlucy9pZG4t\ndGFibGVzL2RiIj5UYWJsZXM8L2E+PC9saT4gLS0+CgkJCQkJCTxsaSBpZD0i\nbmF2X2RvbV9pZG5fc3VibWl0Ij48YSBocmVmPSIvcHJvY2VkdXJlcy9pZG4t\ncmVwb3NpdG9yeS5odG1sIj5TdWJtaXQgYSB0YWJsZTwvYT48L2xpPgoJCQkJ\nCTwvdWw+CgkJCQkJPGxpIGlkPSJuYXZfZG9tX2Ruc3NlYyI+PGEgaHJlZj0i\nL2Ruc3NlYyI+Um9vdCBLZXkgU2lnbmluZyBLZXkgKEROU1NFQyk8L2E+PC9s\naT4KCQkJCQk8dWwgaWQ9Im5hdl9kb21fZG5zc2VjX3N1YiI+CgkJCQkJCTxs\naSBpZD0ibmF2X2RvbV9kbnNzZWNfdG9wIj48YSBocmVmPSIvZG5zc2VjIj5P\ndmVydmlldzwvYT48L2xpPgoJCQkJCQk8bGkgaWQ9Im5hdl9kb21fZG5zc2Vj\nX2tzayI+PGEgaHJlZj0iL2Ruc3NlYy9maWxlcyI+VHJ1c3RzIEFuY2hvcnMg\nYW5kIEtleXM8L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJuYXZfZG9tX2Ruc3Nl\nY19jZXJlbW9uaWVzIj48YSBocmVmPSIvZG5zc2VjL2NlcmVtb25pZXMiPlJv\nb3QgS1NLIENlcmVtb25pZXM8L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJuYXZf\nZG9tX2Ruc3NlY19kcHMiPjxhIGhyZWY9Ii9kbnNzZWMvZHBzIj5QcmFjdGlj\nZSBTdGF0ZW1lbnQ8L2E+PC9saT4KICAgICAgICAgICAgICAgICAgICAgICAg\nPGxpIGlkPSJuYXZfZG9tX2Ruc3NlY190Y3JzIj48YSBocmVmPSIvZG5zc2Vj\nL3RjcnMiPkNvbW11bml0eSBSZXByZXNlbnRhdGl2ZXM8L2E+PC9saT4KCQkJ\nCQk8L3VsPgoJCQkJCTxsaSBpZD0ibmF2X2RvbV9zcGVjaWFsIj48YSBocmVm\nPSIvZG9tYWlucy9yZXNlcnZlZCI+UmVzZXJ2ZWQgRG9tYWluczwvYT48L2xp\nPgoJCQkJPC91bD4KCQkJCTwvZGl2PgoJCQk8L2Rpdj4KCQkJCgoJPC9kaXY+\nCgoJPGZvb3Rlcj4KCQk8ZGl2IGlkPSJmb290ZXIiPgoJCQk8dGFibGUgY2xh\nc3M9Im5hdmlnYXRpb24iPgoJCQkJPHRyPgoJCQkJCTx0ZCBjbGFzcz0ic2Vj\ndGlvbiI+PGEgaHJlZj0iL2RvbWFpbnMiPkRvbWFpbiZuYnNwO05hbWVzPC9h\nPjwvdGQ+CgkJCQkJPHRkIGNsYXNzPSJzdWJzZWN0aW9uIj4KCQkJCQkJPHVs\nPgoJCQkJCQkJPGxpPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QiPlJvb3QgWm9u\nZSBSZWdpc3RyeTwvYT48L2xpPgoJCQkJCQkJPGxpPjxhIGhyZWY9Ii9kb21h\naW5zL2ludCI+LklOVCBSZWdpc3RyeTwvYT48L2xpPgoJCQkJCQkJPGxpPjxh\nIGhyZWY9Ii9kb21haW5zL2FycGEiPi5BUlBBIFJlZ2lzdHJ5PC9hPjwvbGk+\nCgkJCQkJCQk8bGk+PGEgaHJlZj0iL2RvbWFpbnMvaWRuLXRhYmxlcyI+SURO\nIFJlcG9zaXRvcnk8L2E+PC9saT4KCQkJCQkJPC91bD4KCQkJCQk8L3RkPgoJ\nCQkJPC90cj4KCQkJCTx0cj4KCQkJCQk8dGQgY2xhc3M9InNlY3Rpb24iPjxh\nIGhyZWY9Ii9udW1iZXJzIj5OdW1iZXImbmJzcDtSZXNvdXJjZXM8L2E+PC90\nZD4KCQkJCQk8dGQgY2xhc3M9InN1YnNlY3Rpb24iPgoJCQkJCQk8dWw+CgkJ\nCQkJCQk8bGk+PGEgaHJlZj0iL2FidXNlIj5BYnVzZSBJbmZvcm1hdGlvbjwv\nYT48L2xpPgoJCQkJCQk8L3VsPgoJCQkJCTwvdGQ+CgkJCQk8L3RyPgoJCQkJ\nPHRyPgoJCQkJCTx0ZCBjbGFzcz0ic2VjdGlvbiI+PGEgaHJlZj0iL3Byb3Rv\nY29scyI+UHJvdG9jb2xzPC9hPjwvdGQ+CgkJCQkJPHRkIGNsYXNzPSJzdWJz\nZWN0aW9uIj4KCQkJCQkJPHVsPgoJCQkJCQkJPGxpPjxhIGhyZWY9Ii9wcm90\nb2NvbHMiPlByb3RvY29sIFJlZ2lzdHJpZXM8L2E+PC9saT4KCQkJCQkJCTxs\naT48YSBocmVmPSIvdGltZS16b25lcyI+VGltZSBab25lIERhdGFiYXNlPC9h\nPjwvbGk+CgkJCQkJCTwvdWw+CgkJCQkJPC90ZD4KCQkJCTwvdHI+CgkJCQk8\ndHI+CgkJCQkJPHRkIGNsYXNzPSJzZWN0aW9uIj48YSBocmVmPSIvYWJvdXQi\nPkFib3V0Jm5ic3A7VXM8L2E+PC90ZD4KCQkJCQk8dGQgY2xhc3M9InN1YnNl\nY3Rpb24iPgoJCQkJCQk8dWw+CgkJCQkJCQk8bGk+PGEgaHJlZj0iL2Fib3V0\nL3ByZXNlbnRhdGlvbnMiPlByZXNlbnRhdGlvbnM8L2E+PC9saT4KCQkJCQkJ\nCTxsaT48YSBocmVmPSIvcmVwb3J0cyI+UmVwb3J0czwvYT48L2xpPgogICAg\nICAgICAgICAgICAgICAgICAgICAgICAgPGxpPjxhIGhyZWY9Ii9wZXJmb3Jt\nYW5jZSI+UGVyZm9ybWFuY2U8L2E+PC9saT4KCQkJCQkJCTxsaT48YSBocmVm\nPSIvcmV2aWV3cyI+UmV2aWV3czwvYT48L2xpPgogICAgICAgICAgICAgICAg\nICAgICAgICAgICAgPGxpPjxhIGhyZWY9Ii9hYm91dC9leGNlbGxlbmNlIj5F\neGNlbGxlbmNlPC9hPjwvbGk+CgkJCQkJCQk8bGk+PGEgaHJlZj0iL2NvbnRh\nY3QiPkNvbnRhY3QgVXM8L2E+PC9saT4KCQkJCQkJPC91bD4KCQkJCQk8L3Rk\nPgoJCQkJPC90cj4KCQkJPC90YWJsZT4KCiAgICAgICAgICAgIDxkaXYgaWQ9\nImN1c3RvZGlhbiI+CiAgICAgICAgICAgICAgICA8cD5UaGUgSUFOQSBmdW5j\ndGlvbnMgY29vcmRpbmF0ZSB0aGUgSW50ZXJuZXTigJlzIGdsb2JhbGx5IHVu\naXF1ZSBpZGVudGlmaWVycywgYW5kCiAgICAgICAgICAgICAgICAgICAgYXJl\nIHByb3ZpZGVkIGJ5IDxhIGhyZWY9Imh0dHA6Ly9wdGkuaWNhbm4ub3JnIj5Q\ndWJsaWMgVGVjaG5pY2FsIElkZW50aWZpZXJzPC9hPiwgYW4gYWZmaWxpYXRl\nIG9mCiAgICAgICAgICAgICAgICAgICAgPGEgaHJlZj0iaHR0cDovL3d3dy5p\nY2Fubi5vcmcvIj5JQ0FOTjwvYT4uPC9wPgogICAgICAgICAgICA8L2Rpdj4K\nCgkJPC9kaXY+Cgk8L2Zvb3Rlcj4KCQoJCjxzY3JpcHQ+CiQoZG9jdW1lbnQp\nLnJlYWR5KGZ1bmN0aW9uKCkgewoJJCgiI25hdl9kb21fc3BlY2lhbCIpLmFk\nZENsYXNzKCJzZWxlY3RlZCIpCgkkKCIjbmF2X2RvbV9pbnRfc3ViIikuaGlk\nZSgpCgkkKCIjbmF2X2RvbV9pZG5fc3ViIikuaGlkZSgpCgkkKCIjbmF2X2Rv\nbV9kbnNzZWNfc3ViIikuaGlkZSgpCgkkKCIjbmF2X2RvbV90b29sc19zdWIi\nKS5oaWRlKCkKCSQoIiNuYXZfZG9tX3Jvb3Rfc3ViIikuaGlkZSgpCn0pOwo8\nL3NjcmlwdD4KCgkKPC9ib2R5PgoKPC9odG1sPgo=\n" 75 | }, 76 | "http_version": null 77 | }, 78 | "recorded_at": "Wed, 06 Sep 2017 10:46:04 GMT" 79 | } 80 | ], 81 | "recorded_with": "VCR 3.0.3" 82 | } -------------------------------------------------------------------------------- /vcr-sharp-tests/fixtures/overwrite-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "http_interactions": [ 3 | { 4 | "request": { 5 | "method": "get", 6 | "uri": "http://www.iana.org/domains/reserved", 7 | "body": { 8 | "encoding": "US-ASCII", 9 | "base64_string": "" 10 | }, 11 | "headers": { 12 | "Accept-Encoding": [ 13 | "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" 14 | ], 15 | "Accept": [ 16 | "*/*" 17 | ], 18 | "User-Agent": [ 19 | "Ruby" 20 | ], 21 | "Host": [ 22 | "www.iana.org" 23 | ] 24 | } 25 | }, 26 | "response": { 27 | "status": { 28 | "code": 200, 29 | "message": "OK" 30 | }, 31 | "headers": { 32 | "Date": [ 33 | "Wed, 06 Sep 2017 10:32:27 GMT" 34 | ], 35 | "Content-Length": [ 36 | "3093" 37 | ], 38 | "Vary": [ 39 | "Accept-Encoding" 40 | ], 41 | "Last-Modified": [ 42 | "Tue, 21 Jul 2015 00:49:48 GMT" 43 | ], 44 | "Expires": [ 45 | "Wed, 06 Sep 2017 12:30:59 GMT" 46 | ], 47 | "X-Frame-Options": [ 48 | "SAMEORIGIN" 49 | ], 50 | "Content-Security-Policy": [ 51 | "upgrade-insecure-requests" 52 | ], 53 | "Content-Type": [ 54 | "text/html; charset=UTF-8" 55 | ], 56 | "Server": [ 57 | "Apache" 58 | ], 59 | "Strict-Transport-Security": [ 60 | "max-age=47304003; preload" 61 | ], 62 | "X-Cache-Hits": [ 63 | "26" 64 | ], 65 | "Accept-Ranges": [ 66 | "bytes" 67 | ], 68 | "Connection": [ 69 | "keep-alive" 70 | ] 71 | }, 72 | "body": { 73 | "encoding": "ASCII-8BIT", 74 | "base64_string": "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgo8aGVhZD4KCTx0aXRsZT5JQU5BIOKA\nlCBJQU5BLW1hbmFnZWQgUmVzZXJ2ZWQgRG9tYWluczwvdGl0bGU+CgoJPG1l\ndGEgY2hhcnNldD0idXRmLTgiIC8+Cgk8bWV0YSBodHRwLWVxdWl2PSJDb250\nZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIg\nLz4KCTxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZp\nY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSIgLz4KCQoJPGxpbmsgcmVsPSJz\ndHlsZXNoZWV0IiBtZWRpYT0ic2NyZWVuIiBocmVmPSIvX2Nzcy8yMDE1LjEv\nc2NyZWVuLmNzcyIvPgoJPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBtZWRpYT0i\ncHJpbnQiIGhyZWY9Ii9fY3NzLzIwMTUuMS9wcmludC5jc3MiLz4KCTxsaW5r\nIHJlbD0ic2hvcnRjdXQgaWNvbiIgdHlwZT0iaW1hZ2UvaWNvIiBocmVmPSIv\nX2ltZy9ib29rbWFya19pY29uLmljbyIvPgoKCTxzY3JpcHQgdHlwZT0idGV4\ndC9qYXZhc2NyaXB0IiBzcmM9Ii9fanMvMjAxMy4xL2pxdWVyeS5qcyI+PC9z\nY3JpcHQ+Cgk8c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCIgc3JjPSIv\nX2pzLzIwMTMuMS9pYW5hLmpzIj48L3NjcmlwdD4KCgkKCjwvaGVhZD4KCjxi\nb2R5PgoJCgk8aGVhZGVyPgoJCTxkaXYgaWQ9ImhlYWRlciI+CgkJCTxkaXYg\naWQ9ImxvZ28iPgoJCQkJPGEgaHJlZj0iLyI+PGltZyBzcmM9Ii9faW1nLzIw\nMTMuMS9pYW5hLWxvZ28taGVhZGVyLnN2ZyIgYWx0PSJIb21lcGFnZSIvPjwv\nYT4KCQkJPC9kaXY+CgkJCTxkaXYgY2xhc3M9Im5hdmlnYXRpb24iPgoJCQkJ\nPHVsPgoJCQkJCTxsaT48YSBocmVmPSIvZG9tYWlucyI+RG9tYWluczwvYT48\nL2xpPgoJCQkJCTxsaT48YSBocmVmPSIvbnVtYmVycyI+TnVtYmVyczwvYT48\nL2xpPgoJCQkJCTxsaT48YSBocmVmPSIvcHJvdG9jb2xzIj5Qcm90b2NvbHM8\nL2E+PC9saT4KCQkJCQk8bGk+PGEgaHJlZj0iL2Fib3V0Ij5BYm91dCBVczwv\nYT48L2xpPgoJCQkJPC91bD4KCQkJPC9kaXY+CgkJPC9kaXY+Cgk8L2hlYWRl\ncj4KCQoJPGRpdiBpZD0iYm9keSI+CgkKCgkJCTxkaXYgaWQ9Im1haW5fcmln\naHQiPgoKCgk8aDE+SUFOQS1tYW5hZ2VkIFJlc2VydmVkIERvbWFpbnM8L2gx\nPgoKCTxwPkNlcnRhaW4gZG9tYWlucyBhcmUgc2V0IGFzaWRlLCBhbmQgbm9t\naW5hbGx5IHJlZ2lzdGVyZWQgdG8gJmxkcXVvO0lBTkEmcmRxdW87LCBmb3Ig\nc3BlY2lmaWMKCQlwb2xpY3kgb3IgdGVjaG5pY2FsIHB1cnBvc2VzLjwvcD4K\nCgk8aDI+RXhhbXBsZSBkb21haW5zPC9oMj4KCQoJPHA+QXMgZGVzY3JpYmVk\nIGluIDxhIGhyZWY9Ii9nby9yZmMyNjA2Ij5SRkMgMjYwNjwvYT4gYW5kIDxh\nIGhyZWY9Ii9nby9yZmM2NzYxIj5SRkMgNjc2MTwvYT4sCglhIG51bWJlciBv\nZiBkb21haW5zIHN1Y2ggYXMgPHNwYW4gY2xhc3M9ImRvbWFpbiBsYWJlbCI+\nZXhhbXBsZS5jb208L3NwYW4+IGFuZCA8c3BhbiBjbGFzcz0iZG9tYWluIGxh\nYmVsIj5leGFtcGxlLm9yZzwvc3Bhbj4KCWFyZSBtYWludGFpbmVkIGZvciBk\nb2N1bWVudGF0aW9uIHB1cnBvc2VzLiBUaGVzZSBkb21haW5zIG1heSBiZSB1\nc2VkIGFzIGlsbHVzdHJhdGl2ZQoJZXhhbXBsZXMgaW4gZG9jdW1lbnRzIHdp\ndGhvdXQgcHJpb3IgY29vcmRpbmF0aW9uIHdpdGggdXMuIFRoZXkgYXJlIAoJ\nbm90IGF2YWlsYWJsZSBmb3IgcmVnaXN0cmF0aW9uIG9yIHRyYW5zZmVyLjwv\ncD4KCgk8aDI+VGVzdCBJRE4gdG9wLWxldmVsIGRvbWFpbnM8L2gyPgoKCSAg\nICAgICAgPHA+VGhlc2UgZG9tYWlucyB3ZXJlIHRlbXBvcmFyaWx5IGRlbGVn\nYXRlZCBieSBJQU5BIGZvciB0aGUgPGEgaHJlZj0iaHR0cDovL3d3dy5pY2Fu\nbi5vcmcvdG9waWNzL2lkbi8iPklETiBFdmFsdWF0aW9uPC9hPiBiZWluZyBj\nb25kdWN0ZWQgYnkgPGEgaHJlZj0iaHR0cDovL3d3dy5pY2Fubi5vcmcvIj5J\nQ0FOTjwvYT4uPC9wPgoKCQk8ZGl2IGNsYXNzPSJpYW5hLXRhYmxlLWZyYW1l\nIj4KCQk8dGFibGUgaWQ9ImFycGEtdGFibGUiIGNsYXNzPSJpYW5hLXRhYmxl\nIj4KCQkJPHRoZWFkPgoJCQk8dHI+PHRoPkRvbWFpbjwvdGg+PHRoPkRvbWFp\nbiAoQS1sYWJlbCk8L3RoPjx0aD5MYW5ndWFnZTwvdGg+PHRoPlNjcmlwdDwv\ndGg+PC90cj4KCQkJPC90aGVhZD4KCQkJPHRib2R5PgoJCQk8dHI+PHRkPiYj\nMTU3MzsmIzE1ODI7JiMxNTc4OyYjMTU3NjsmIzE1NzU7JiMxNTg1OzwvdGQ+\nPHRkPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21h\naW5zL3Jvb3QvZGIveG4tLWtnYmVjaHR2Lmh0bWwiPlhOLS1LR0JFQ0hUVjwv\nYT48L3NwYW4+PC90ZD4KCTx0ZD5BcmFiaWM8L3RkPjx0ZD5BcmFiaWM8L3Rk\nPjwvdHI+Cgk8dHI+PHRkPiYjMTU3MDsmIzE1ODY7JiMxNjA1OyYjMTU3NTsm\nIzE3NDA7JiMxNTg4OyYjMTc0MDs8L3RkPjx0ZD48c3BhbiBjbGFzcz0iZG9t\nYWluIGxhYmVsIj48YSBocmVmPSIvZG9tYWlucy9yb290L2RiL3huLS1oZ2Jr\nNmFqN2Y1M2JiYS5odG1sIj5YTi0tSEdCSzZBSjdGNTNCQkE8L2E+PC9zcGFu\nPjwvdGQ+Cgk8dGQ+UGVyc2lhbjwvdGQ+PHRkPkFyYWJpYzwvdGQ+PC90cj4K\nCTx0cj48dGQ+JiMyNzk3OTsmIzM1Nzk3OzwvdGQ+PHRkPjxzcGFuIGNsYXNz\nPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QvZGIveG4t\nLTB6d201NmQuaHRtbCI+WE4tLTBaV001NkQ8L2E+PC9zcGFuPjwvdGQ+Cgk8\ndGQ+Q2hpbmVzZTwvdGQ+PHRkPkhhbiAoU2ltcGxpZmllZCB2YXJpYW50KTwv\ndGQ+PC90cj4KCTx0cj48dGQ+JiMyODIwNDsmIzM1NDMwOzwvdGQ+PHRkPjxz\ncGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21haW5zL3Jv\nb3QvZGIveG4tLWc2dzI1MWQuaHRtbCI+WE4tLUc2VzI1MUQ8L2E+PC9zcGFu\nPjwvdGQ+Cgk8dGQ+Q2hpbmVzZTwvdGQ+PHRkPkhhbiAoVHJhZGl0aW9uYWwg\ndmFyaWFudCk8L3RkPjwvdHI+Cgk8dHI+PHRkPiYjMTA4MDsmIzEwODk7JiMx\nMDg3OyYjMTA5OTsmIzEwOTA7JiMxMDcyOyYjMTA4NTsmIzEwODA7JiMxMDc3\nOzwvdGQ+PHRkPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9\nIi9kb21haW5zL3Jvb3QvZGIveG4tLTgwYWtoYnlrbmo0Zi5odG1sIj5YTi0t\nODBBS0hCWUtOSjRGPC9hPjwvc3Bhbj48L3RkPgoJPHRkPlJ1c3NpYW48L3Rk\nPjx0ZD5DeXJpbGxpYzwvdGQ+PC90cj4KCTx0cj48dGQ+JiMyMzQ2OyYjMjM1\nMjsmIzIzNjg7JiMyMzI1OyYjMjM4MTsmIzIzNTk7JiMyMzY2OzwvdGQ+PHRk\nPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21haW5z\nL3Jvb3QvZGIveG4tLTExYjViczNhOWFqNmcuaHRtbCI+WE4tLTExQjVCUzNB\nOUFKNkc8L2E+PC9zcGFuPjwvdGQ+Cgk8dGQ+SGluZGk8L3RkPjx0ZD5EZXZh\nbmFnYXJpIChOYWdhcmkpPC90ZD48L3RyPgoJPHRyPjx0ZD4mIzk0ODsmIzk1\nOTsmIzk1NDsmIzk1MzsmIzk1NjsmIzk0Mjs8L3RkPjx0ZD48c3BhbiBjbGFz\ncz0iZG9tYWluIGxhYmVsIj48YSBocmVmPSIvZG9tYWlucy9yb290L2RiL3hu\nLS1qeGFscGRscC5odG1sIj5YTi0tSlhBTFBETFA8L2E+PC9zcGFuPjwvdGQ+\nCgk8dGQ+R3JlZWssIE1vZGVybiAoMTQ1My0pPC90ZD48dGQ+R3JlZWs8L3Rk\nPjwvdHI+Cgk8dHI+PHRkPiYjNTM1ODA7JiM0OTgyODsmIzUzOTQ0OzwvdGQ+\nPHRkPjxzcGFuIGNsYXNzPSJkb21haW4gbGFiZWwiPjxhIGhyZWY9Ii9kb21h\naW5zL3Jvb3QvZGIveG4tLTl0NGIxMXlpNWEuaHRtbCI+WE4tLTlUNEIxMVlJ\nNUE8L2E+PC9zcGFuPjwvdGQ+Cgk8dGQ+S29yZWFuPC90ZD48dGQ+SGFuZ3Vs\nIChIYW5nJiN4MTZEO2wsIEhhbmdldWwpPC90ZD48L3RyPgoJPHRyPjx0ZD4m\nIzE0OTY7JiMxNTA2OyYjMTUwNTsmIzE0OTY7PC90ZD48dGQ+PHNwYW4gY2xh\nc3M9ImRvbWFpbiBsYWJlbCI+PGEgaHJlZj0iL2RvbWFpbnMvcm9vdC9kYi94\nbi0tZGViYTBhZC5odG1sIj5YTi0tREVCQTBBRDwvYT48L3NwYW4+PC90ZD4K\nCTx0ZD5ZaWRkaXNoPC90ZD48dGQ+SGVicmV3PC90ZD48L3RyPgoJPHRyPjx0\nZD4mIzEyNDg2OyYjMTI0NzM7JiMxMjQ4ODs8L3RkPjx0ZD48c3BhbiBjbGFz\ncz0iZG9tYWluIGxhYmVsIj48YSBocmVmPSIvZG9tYWlucy9yb290L2RiL3hu\nLS16Y2t6YWguaHRtbCI+WE4tLVpDS1pBSDwvYT48L3NwYW4+PC90ZD4KCTx0\nZD5KYXBhbmVzZTwvdGQ+PHRkPkthdGFrYW5hPC90ZD48L3RyPgoJPHRyPjx0\nZD4mIzI5ODY7JiMyOTkyOyYjMzAwNzsmIzI5NzU7JiMzMDIxOyYjMjk3MDsm\nIzMwMTY7PC90ZD48dGQ+PHNwYW4gY2xhc3M9ImRvbWFpbiBsYWJlbCI+PGEg\naHJlZj0iL2RvbWFpbnMvcm9vdC9kYi94bi0taGxjajZheWE5ZXNjN2EuaHRt\nbCI+WE4tLUhMQ0o2QVlBOUVTQzdBPC9hPjwvc3Bhbj48L3RkPgoJPHRkPlRh\nbWlsPC90ZD48dGQ+VGFtaWw8L3RkPjwvdHI+PC90Ym9keT4KCQk8L3RhYmxl\nPgoJICAgICAgICA8L2Rpdj4KCgk8aDI+UG9saWN5LXJlc2VydmVkIGRvbWFp\nbnM8L2gyPgoJCgk8cD5XZSBhY3QgYXMgYm90aCB0aGUgcmVnaXN0cmFudCBh\nbmQgcmVnaXN0cmFyIGZvciBhIHNlbGVjdCBudW1iZXIgb2YgZG9tYWlucwoJ\nCXdoaWNoIGhhdmUgYmVlbiByZXNlcnZlZCB1bmRlciBwb2xpY3kgZ3JvdW5k\ncy4gVGhlc2UgZXhjbHVzaW9ucyBhcmUKCQl0eXBpY2FsbHkgaW5kaWNhdGVk\nIGluIGVpdGhlciB0ZWNobmljYWwgc3RhbmRhcmRzIChSRkMgZG9jdW1lbnRz\nKSwKCQlvciA8YSBocmVmPSJodHRwOi8vd3d3LmljYW5uLm9yZy9lbi9yZWdp\nc3RyaWVzL2FncmVlbWVudHMuaHRtIj5jb250cmFjdHVhbCBsaW1pdGF0aW9u\nczwvYT4uPC9wPgoJCQoJCTxwPkRvbWFpbnMgd2hpY2ggYXJlIGRlc2NyaWJl\nZCBhcyByZWdpc3RlcmVkIHRvIElBTkEgb3IgSUNBTk4gb24gcG9saWN5CgkJ\nZ3JvdW5kcyBhcmUgbm90IGF2YWlsYWJsZSBmb3IgcmVnaXN0cmF0aW9uIG9y\nIHRyYW5zZmVyLCB3aXRoIHRoZSBleGNlcHRpb24KCQlvZiA8c3BhbiBjbGFz\ncz0iZG9tYWluIGxhYmVsIj48aT5jb3VudHJ5LW5hbWU8L2k+LmluZm88L3Nw\nYW4+IGRvbWFpbnMuIFRoZXNlIGRvbWFpbnMgYXJlIGF2YWlsYWJsZSBmb3Ig\ncmVsZWFzZQoJCWJ5IHRoZSBJQ0FOTiBHb3Zlcm5tZW50YWwgQWR2aXNvcnkg\nQ29tbWl0dGVlIFNlY3JldGFyaWF0LjwvcD4KCiAgICA8aDI+T3RoZXIgU3Bl\nY2lhbC1Vc2UgRG9tYWluczwvaDI+CgogICAgPHA+VGhlcmUgaXMgYWRkaXRp\nb25hbGx5IGEgPGEgaHJlZj0iL2Fzc2lnbm1lbnRzL3NwZWNpYWwtdXNlLWRv\nbWFpbi1uYW1lcyI+U3BlY2lhbC1Vc2UgRG9tYWluIE5hbWVzPC9hPiByZWdp\nc3RyeSBkb2N1bWVudGluZyBzcGVjaWFsLXVzZSBkb21haW5zIGRlc2lnbmF0\nZWQgYnkgdGVjaG5pY2FsIHN0YW5kYXJkcy4gRm9yIGZ1cnRoZXIgaW5mb3Jt\nYXRpb24sIHNlZSA8YSBocmVmPSIvZ28vcmZjNjc2MSI+U3BlY2lhbC1Vc2Ug\nRG9tYWluIE5hbWVzPC9hPiAoUkZDIDY3NjEpLjwvcD4KCQoKCQkJPC9kaXY+\nCgkJCQoJCQk8ZGl2IGlkPSJzaWRlYmFyX2xlZnQiPgoJCQkJPGRpdiBjbGFz\ncz0ibmF2aWdhdGlvbl9ib3giPgoJCQkJPGgyPkRvbWFpbiBOYW1lczwvaDI+\nCgkJCQk8dWw+CgkJCQkJPGxpIGlkPSJuYXZfZG9tX3RvcCI+PGEgaHJlZj0i\nL2RvbWFpbnMiPk92ZXJ2aWV3PC9hPjwvbGk+CgkJCQkJPGxpIGlkPSJuYXZf\nZG9tX3Jvb3QiPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QiPlJvb3QgWm9uZSBN\nYW5hZ2VtZW50PC9hPjwvbGk+CgkJCQkJPHVsIGlkPSJuYXZfZG9tX3Jvb3Rf\nc3ViIj4KCQkJCQkJPGxpIGlkPSJuYXZfZG9tX3Jvb3RfdG9wIj48YSBocmVm\nPSIvZG9tYWlucy9yb290Ij5PdmVydmlldzwvYT48L2xpPgoJCQkJCQk8bGkg\naWQ9Im5hdl9kb21fcm9vdF9kYiI+PGEgaHJlZj0iL2RvbWFpbnMvcm9vdC9k\nYiI+Um9vdCBEYXRhYmFzZTwvYT48L2xpPgoJCQkJCQk8bGkgaWQ9Im5hdl9k\nb21fcm9vdF9maWxlcyI+PGEgaHJlZj0iL2RvbWFpbnMvcm9vdC9maWxlcyI+\nSGludCBhbmQgWm9uZSBGaWxlczwvYT48L2xpPgoJCQkJCQk8bGkgaWQ9Im5h\ndl9kb21fcm9vdF9tYW5hZ2UiPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QvbWFu\nYWdlIj5DaGFuZ2UgUmVxdWVzdHM8L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJu\nYXZfZG9tX3Jvb3RfcHJvY2VkdXJlcyI+PGEgaHJlZj0iL2RvbWFpbnMvcm9v\ndC9oZWxwIj5JbnN0cnVjdGlvbnMgJmFtcDsgR3VpZGVzPC9hPjwvbGk+CgkJ\nCQkJCTxsaSBpZD0ibmF2X2RvbV9yb290X3NlcnZlcnMiPjxhIGhyZWY9Ii9k\nb21haW5zL3Jvb3Qvc2VydmVycyI+Um9vdCBTZXJ2ZXJzPC9hPjwvbGk+CgkJ\nCQkJPC91bD4KCQkJCQk8bGkgaWQ9Im5hdl9kb21faW50Ij48YSBocmVmPSIv\nZG9tYWlucy9pbnQiPi5JTlQgUmVnaXN0cnk8L2E+PC9saT4KCQkJCQk8dWwg\naWQ9Im5hdl9kb21faW50X3N1YiI+CgkJCQkJCTxsaSBpZD0ibmF2X2RvbV9p\nbnRfdG9wIj48YSBocmVmPSIvZG9tYWlucy9pbnQiPk92ZXJ2aWV3PC9hPjwv\nbGk+CgkJCQkJCTxsaSBpZD0ibmF2X2RvbV9pbnRfbWFuYWdlIj48YSBocmVm\nPSIvZG9tYWlucy9pbnQvbWFuYWdlIj5SZWdpc3Rlci9tb2RpZnkgYW4gLklO\nVCBkb21haW48L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJuYXZfZG9tX2ludF9w\nb2xpY3kiPjxhIGhyZWY9Ii9kb21haW5zL2ludC9wb2xpY3kiPkVsaWdpYmls\naXR5PC9hPjwvbGk+CgkJCQkJPC91bD4KCQkJCQk8bGkgaWQ9Im5hdl9kb21f\nYXJwYSI+PGEgaHJlZj0iL2RvbWFpbnMvYXJwYSI+LkFSUEEgUmVnaXN0cnk8\nL2E+PC9saT4KCQkJCQk8bGkgaWQ9Im5hdl9kb21faWRuIj48YSBocmVmPSIv\nZG9tYWlucy9pZG4tdGFibGVzIj5JRE4gUHJhY3RpY2VzIFJlcG9zaXRvcnk8\nL2E+PC9saT4KCQkJCQk8dWwgaWQ9Im5hdl9kb21faWRuX3N1YiI+CgkJCQkJ\nCTxsaSBpZD0ibmF2X2RvbV9pZG5fdG9wIj48YSBocmVmPSIvZG9tYWlucy9p\nZG4tdGFibGVzIj5PdmVydmlldzwvYT48L2xpPgoJCQkJCQk8IS0tIDxsaSBp\nZD0ibmF2X2RvbV9pZG5fdGFibGVzIj48YSBocmVmPSIvZG9tYWlucy9pZG4t\ndGFibGVzL2RiIj5UYWJsZXM8L2E+PC9saT4gLS0+CgkJCQkJCTxsaSBpZD0i\nbmF2X2RvbV9pZG5fc3VibWl0Ij48YSBocmVmPSIvcHJvY2VkdXJlcy9pZG4t\ncmVwb3NpdG9yeS5odG1sIj5TdWJtaXQgYSB0YWJsZTwvYT48L2xpPgoJCQkJ\nCTwvdWw+CgkJCQkJPGxpIGlkPSJuYXZfZG9tX2Ruc3NlYyI+PGEgaHJlZj0i\nL2Ruc3NlYyI+Um9vdCBLZXkgU2lnbmluZyBLZXkgKEROU1NFQyk8L2E+PC9s\naT4KCQkJCQk8dWwgaWQ9Im5hdl9kb21fZG5zc2VjX3N1YiI+CgkJCQkJCTxs\naSBpZD0ibmF2X2RvbV9kbnNzZWNfdG9wIj48YSBocmVmPSIvZG5zc2VjIj5P\ndmVydmlldzwvYT48L2xpPgoJCQkJCQk8bGkgaWQ9Im5hdl9kb21fZG5zc2Vj\nX2tzayI+PGEgaHJlZj0iL2Ruc3NlYy9maWxlcyI+VHJ1c3RzIEFuY2hvcnMg\nYW5kIEtleXM8L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJuYXZfZG9tX2Ruc3Nl\nY19jZXJlbW9uaWVzIj48YSBocmVmPSIvZG5zc2VjL2NlcmVtb25pZXMiPlJv\nb3QgS1NLIENlcmVtb25pZXM8L2E+PC9saT4KCQkJCQkJPGxpIGlkPSJuYXZf\nZG9tX2Ruc3NlY19kcHMiPjxhIGhyZWY9Ii9kbnNzZWMvZHBzIj5QcmFjdGlj\nZSBTdGF0ZW1lbnQ8L2E+PC9saT4KICAgICAgICAgICAgICAgICAgICAgICAg\nPGxpIGlkPSJuYXZfZG9tX2Ruc3NlY190Y3JzIj48YSBocmVmPSIvZG5zc2Vj\nL3RjcnMiPkNvbW11bml0eSBSZXByZXNlbnRhdGl2ZXM8L2E+PC9saT4KCQkJ\nCQk8L3VsPgoJCQkJCTxsaSBpZD0ibmF2X2RvbV9zcGVjaWFsIj48YSBocmVm\nPSIvZG9tYWlucy9yZXNlcnZlZCI+UmVzZXJ2ZWQgRG9tYWluczwvYT48L2xp\nPgoJCQkJPC91bD4KCQkJCTwvZGl2PgoJCQk8L2Rpdj4KCQkJCgoJPC9kaXY+\nCgoJPGZvb3Rlcj4KCQk8ZGl2IGlkPSJmb290ZXIiPgoJCQk8dGFibGUgY2xh\nc3M9Im5hdmlnYXRpb24iPgoJCQkJPHRyPgoJCQkJCTx0ZCBjbGFzcz0ic2Vj\ndGlvbiI+PGEgaHJlZj0iL2RvbWFpbnMiPkRvbWFpbiZuYnNwO05hbWVzPC9h\nPjwvdGQ+CgkJCQkJPHRkIGNsYXNzPSJzdWJzZWN0aW9uIj4KCQkJCQkJPHVs\nPgoJCQkJCQkJPGxpPjxhIGhyZWY9Ii9kb21haW5zL3Jvb3QiPlJvb3QgWm9u\nZSBSZWdpc3RyeTwvYT48L2xpPgoJCQkJCQkJPGxpPjxhIGhyZWY9Ii9kb21h\naW5zL2ludCI+LklOVCBSZWdpc3RyeTwvYT48L2xpPgoJCQkJCQkJPGxpPjxh\nIGhyZWY9Ii9kb21haW5zL2FycGEiPi5BUlBBIFJlZ2lzdHJ5PC9hPjwvbGk+\nCgkJCQkJCQk8bGk+PGEgaHJlZj0iL2RvbWFpbnMvaWRuLXRhYmxlcyI+SURO\nIFJlcG9zaXRvcnk8L2E+PC9saT4KCQkJCQkJPC91bD4KCQkJCQk8L3RkPgoJ\nCQkJPC90cj4KCQkJCTx0cj4KCQkJCQk8dGQgY2xhc3M9InNlY3Rpb24iPjxh\nIGhyZWY9Ii9udW1iZXJzIj5OdW1iZXImbmJzcDtSZXNvdXJjZXM8L2E+PC90\nZD4KCQkJCQk8dGQgY2xhc3M9InN1YnNlY3Rpb24iPgoJCQkJCQk8dWw+CgkJ\nCQkJCQk8bGk+PGEgaHJlZj0iL2FidXNlIj5BYnVzZSBJbmZvcm1hdGlvbjwv\nYT48L2xpPgoJCQkJCQk8L3VsPgoJCQkJCTwvdGQ+CgkJCQk8L3RyPgoJCQkJ\nPHRyPgoJCQkJCTx0ZCBjbGFzcz0ic2VjdGlvbiI+PGEgaHJlZj0iL3Byb3Rv\nY29scyI+UHJvdG9jb2xzPC9hPjwvdGQ+CgkJCQkJPHRkIGNsYXNzPSJzdWJz\nZWN0aW9uIj4KCQkJCQkJPHVsPgoJCQkJCQkJPGxpPjxhIGhyZWY9Ii9wcm90\nb2NvbHMiPlByb3RvY29sIFJlZ2lzdHJpZXM8L2E+PC9saT4KCQkJCQkJCTxs\naT48YSBocmVmPSIvdGltZS16b25lcyI+VGltZSBab25lIERhdGFiYXNlPC9h\nPjwvbGk+CgkJCQkJCTwvdWw+CgkJCQkJPC90ZD4KCQkJCTwvdHI+CgkJCQk8\ndHI+CgkJCQkJPHRkIGNsYXNzPSJzZWN0aW9uIj48YSBocmVmPSIvYWJvdXQi\nPkFib3V0Jm5ic3A7VXM8L2E+PC90ZD4KCQkJCQk8dGQgY2xhc3M9InN1YnNl\nY3Rpb24iPgoJCQkJCQk8dWw+CgkJCQkJCQk8bGk+PGEgaHJlZj0iL2Fib3V0\nL3ByZXNlbnRhdGlvbnMiPlByZXNlbnRhdGlvbnM8L2E+PC9saT4KCQkJCQkJ\nCTxsaT48YSBocmVmPSIvcmVwb3J0cyI+UmVwb3J0czwvYT48L2xpPgogICAg\nICAgICAgICAgICAgICAgICAgICAgICAgPGxpPjxhIGhyZWY9Ii9wZXJmb3Jt\nYW5jZSI+UGVyZm9ybWFuY2U8L2E+PC9saT4KCQkJCQkJCTxsaT48YSBocmVm\nPSIvcmV2aWV3cyI+UmV2aWV3czwvYT48L2xpPgogICAgICAgICAgICAgICAg\nICAgICAgICAgICAgPGxpPjxhIGhyZWY9Ii9hYm91dC9leGNlbGxlbmNlIj5F\neGNlbGxlbmNlPC9hPjwvbGk+CgkJCQkJCQk8bGk+PGEgaHJlZj0iL2NvbnRh\nY3QiPkNvbnRhY3QgVXM8L2E+PC9saT4KCQkJCQkJPC91bD4KCQkJCQk8L3Rk\nPgoJCQkJPC90cj4KCQkJPC90YWJsZT4KCiAgICAgICAgICAgIDxkaXYgaWQ9\nImN1c3RvZGlhbiI+CiAgICAgICAgICAgICAgICA8cD5UaGUgSUFOQSBmdW5j\ndGlvbnMgY29vcmRpbmF0ZSB0aGUgSW50ZXJuZXTigJlzIGdsb2JhbGx5IHVu\naXF1ZSBpZGVudGlmaWVycywgYW5kCiAgICAgICAgICAgICAgICAgICAgYXJl\nIHByb3ZpZGVkIGJ5IDxhIGhyZWY9Imh0dHA6Ly9wdGkuaWNhbm4ub3JnIj5Q\ndWJsaWMgVGVjaG5pY2FsIElkZW50aWZpZXJzPC9hPiwgYW4gYWZmaWxpYXRl\nIG9mCiAgICAgICAgICAgICAgICAgICAgPGEgaHJlZj0iaHR0cDovL3d3dy5p\nY2Fubi5vcmcvIj5JQ0FOTjwvYT4uPC9wPgogICAgICAgICAgICA8L2Rpdj4K\nCgkJPC9kaXY+Cgk8L2Zvb3Rlcj4KCQoJCjxzY3JpcHQ+CiQoZG9jdW1lbnQp\nLnJlYWR5KGZ1bmN0aW9uKCkgewoJJCgiI25hdl9kb21fc3BlY2lhbCIpLmFk\nZENsYXNzKCJzZWxlY3RlZCIpCgkkKCIjbmF2X2RvbV9pbnRfc3ViIikuaGlk\nZSgpCgkkKCIjbmF2X2RvbV9pZG5fc3ViIikuaGlkZSgpCgkkKCIjbmF2X2Rv\nbV9kbnNzZWNfc3ViIikuaGlkZSgpCgkkKCIjbmF2X2RvbV90b29sc19zdWIi\nKS5oaWRlKCkKCSQoIiNuYXZfZG9tX3Jvb3Rfc3ViIikuaGlkZSgpCn0pOwo8\nL3NjcmlwdD4KCgkKPC9ib2R5PgoKPC9odG1sPgo=\n" 75 | }, 76 | "http_version": null 77 | }, 78 | "recorded_at": "Wed, 06 Sep 2017 10:46:04 GMT" 79 | } 80 | ], 81 | "recorded_with": "VCR 3.0.3" 82 | } -------------------------------------------------------------------------------- /vcr-sharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.12 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VCRSharp.Tests", "vcr-sharp-tests\VCRSharp.Tests.csproj", "{3D505D30-B2A1-438F-B8E2-347C3A7BE440}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{5E553785-0677-4AC9-A7E0-93929422E196}" 9 | ProjectSection(SolutionItems) = preProject 10 | appveyor.yml = appveyor.yml 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VCRSharp.Benchmarks", "VCRSharp.Benchmarks\VCRSharp.Benchmarks.csproj", "{D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|x64 = Debug|x64 20 | Debug|x86 = Debug|x86 21 | Release|Any CPU = Release|Any CPU 22 | Release|x64 = Release|x64 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Debug|x64.ActiveCfg = Debug|Any CPU 29 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Debug|x64.Build.0 = Debug|Any CPU 30 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Debug|x86.ActiveCfg = Debug|Any CPU 31 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Debug|x86.Build.0 = Debug|Any CPU 32 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Release|x64.ActiveCfg = Release|Any CPU 35 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Release|x64.Build.0 = Release|Any CPU 36 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Release|x86.ActiveCfg = Release|Any CPU 37 | {3D505D30-B2A1-438F-B8E2-347C3A7BE440}.Release|x86.Build.0 = Release|Any CPU 38 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Debug|x64.ActiveCfg = Debug|Any CPU 41 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Debug|x64.Build.0 = Debug|Any CPU 42 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Debug|x86.ActiveCfg = Debug|Any CPU 43 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Debug|x86.Build.0 = Debug|Any CPU 44 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Release|x64.ActiveCfg = Release|Any CPU 47 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Release|x64.Build.0 = Release|Any CPU 48 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Release|x86.ActiveCfg = Release|Any CPU 49 | {D3C5E0D1-3AEB-467D-9B70-CDDCCB541C56}.Release|x86.Build.0 = Release|Any CPU 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | GlobalSection(ExtensibilityGlobals) = postSolution 55 | SolutionGuid = {EC001710-E020-42AD-9606-608728BFED58} 56 | EndGlobalSection 57 | EndGlobal 58 | --------------------------------------------------------------------------------