public static async void NonSuccessfulHttpRequestThrows()
        {
            var client = Substitute.For<IElasticsearchClient>();

            client.SearchAsync<string>(
                    "_all",
                    "docType",
                    @"{""timeout"":""10s""}",
                    Arg.Any<Func<SearchRequestParameters, SearchRequestParameters>>())
                .Returns(Task.FromResult(ElasticsearchResponse<string>.Create(
                    new ConnectionConfiguration(),
                    404,
                    "_search",
                    "_all",
                    new byte[0])));

            var localConnection = new ElasticNetConnection(client);
            var request = new SearchRequest { DocumentType = "docType" };
            var formatter = new SearchRequestFormatter(localConnection, mapping, request);

            var ex = await Record.ExceptionAsync(() => localConnection.SearchAsync(
                formatter.Body,
                request,
                CancellationToken.None,
                log));

            Assert.IsType<HttpRequestException>(ex);
            Assert.Equal("Response status code does not indicate success: 404 (Not Found).", ex.Message);
        }
        public static async Task ShouldCallElasticSearchClient()
        {
            var spyLog = new SpyLog();

            var mockConnection = Substitute.For<IElasticConnection>();

            mockConnection.Index.Returns("SearchIndex");
            mockConnection.Options.Returns(new ElasticConnectionOptions());
            mockConnection.Timeout.Returns(TimeSpan.FromSeconds(10));

            var request = new SearchRequest { DocumentType = "abc123", Size = 2112 };
            var token = new CancellationToken();

            var processor = new ElasticRequestProcessor(mockConnection, mapping, spyLog, retryPolicy);

            await processor.SearchAsync(request, token);

#pragma warning disable 4014 // Remove this and await the SearchAsync below once NSubstitute 1.8.3 available
            mockConnection.Received(1).SearchAsync(
#pragma warning restore 4014
               @"{""size"":2112,""timeout"":""10s""}",
               request,
               token,
               spyLog
               );
        }
        /// <inheritdoc/>
        public override async Task<ElasticResponse> SearchAsync(
            string body,
            SearchRequest searchRequest,
            CancellationToken token,
            ILog log)
        {
            var stopwatch = Stopwatch.StartNew();

            var response = await Task.Run(() => client.SearchAsync<string>(
                                Index ?? "_all",
                                searchRequest.DocumentType,
                                body,
                                searchParams => SetRequestParameters(searchParams, searchRequest)),
                            token);

            stopwatch.Stop();

            log.Log(TraceEventType.Verbose, null, null, "Request: POST {0}", response.RequestUrl);
            log.Log(TraceEventType.Verbose, null, null, "Body:\n{0}", body);
            log.Log(TraceEventType.Verbose, null, null, "Response: {0} {1} (in {2}ms)", response.HttpStatusCode, response.HttpStatusCode.HasValue ? ((HttpStatusCode)response.HttpStatusCode).ToString() : "", stopwatch.ElapsedMilliseconds);

            if (!response.Success)
                throw new HttpRequestException("Response status code does not indicate success: 404 (Not Found).");

            return ParseResponse(response.Response, log);
        }
        public static async void SearchAsyncThrowsTaskCancelledExceptionWithSubsequentlyCancelledCancellationToken()
        {
            var request = new SearchRequest { DocumentType = "docType" };
            var processor = new ElasticRequestProcessor(connection, mapping, log, retryPolicy);

            var ex = await Record.ExceptionAsync(() => processor.SearchAsync(request, new CancellationTokenSource(500).Token));

            Assert.IsType<TaskCanceledException>(ex);
        }
        SearchRequestParameters SetRequestParameters(
            SearchRequestParameters searchRequestParameters,
            SearchRequest searchRequest)
        {
            if (string.Equals(searchRequest.SearchType, "count", StringComparison.OrdinalIgnoreCase))
                searchRequestParameters.SearchType(SearchType.Count);

            return searchRequestParameters;
        }
        public void BodyContainsMinScoreWhenSpecified()
        {
            var searchRequest = new SearchRequest { MinScore = 1.3 };

            var formatter = new SearchRequestFormatter(defaultConnection, mapping, searchRequest);
            var body = JObject.Parse(formatter.Body);

            var result = body.TraverseWithAssert("min_score");
            Assert.Equal(searchRequest.MinScore.ToString(), result);
        }
        public void ConstructorSetsProperties()
        {
            var expectedSearch = new SearchRequest { DocumentType = "someType" };
            var expectedMaterializer = new ListHitsElasticMaterializer(o => o, typeof(ElasticConnectionTests));

            var result = new ElasticTranslateResult(expectedSearch, expectedMaterializer);

            Assert.Same(expectedSearch, result.SearchRequest);
            Assert.Same(expectedMaterializer, result.Materializer);
        }
        public void ConstructorHasSensibleDefaultValues()
        {
            var request = new SearchRequest();

            Assert.Equal(0, request.From);
            Assert.Null(request.Size);
            Assert.Empty(request.Fields);
            Assert.Empty(request.SortOptions);
            Assert.Null(request.Filter);
        }
        public static async Task NoAuthorizationWithEmptyUserName()
        {
            var messageHandler = new SpyMessageHandler();
            var localConnection = new ElasticConnection(messageHandler, new Uri("http://localhost"));
            var processor = new ElasticRequestProcessor(localConnection, mapping, log, retryPolicy);
            var request = new SearchRequest { DocumentType = "docType" };

            await processor.SearchAsync(request);

            Assert.Null(messageHandler.Request.Headers.Authorization);
        }
        public void BodyContainsStatisticalFacet()
        {
            var expectedFacet = new StatisticalFacet("TotalSales", "OrderTotal");
            var searchRequest = new SearchRequest { Facets = new List<IFacet>(new[] { expectedFacet }) };

            var formatter = new SearchRequestFormatter(defaultConnection, mapping, searchRequest);
            var body = JObject.Parse(formatter.Body);

            var result = body.TraverseWithAssert("facets", expectedFacet.Name, expectedFacet.Type, "field");
            Assert.Equal(expectedFacet.Fields[0], result.ToString());
        }
        public void BodyContainsTermsFacetWithNoSizeWhenNotSpecified()
        {
            var expectedFacet = new TermsFacet("Totals", null, "OrderTotal");
            var searchRequest = new SearchRequest { Facets = new List<IFacet>(new[] { expectedFacet }) };

            var formatter = new SearchRequestFormatter(defaultConnection, mapping, searchRequest);
            var body = JObject.Parse(formatter.Body);

            var result = body.TraverseWithAssert("facets", expectedFacet.Name, expectedFacet.Type);

            Assert.False(result.Contains("size"));
        }
        public static async void NonSuccessfulHttpRequestThrows()
        {
            var messageHandler = new SpyMessageHandler { Response = { StatusCode = HttpStatusCode.NotFound } };
            var localConnection = new ElasticConnection(messageHandler, new Uri("http://localhost"), "myUser", "myPass");
            var processor = new ElasticRequestProcessor(localConnection, mapping, log, retryPolicy);
            var request = new SearchRequest { DocumentType = "docType" };

            var ex = await Record.ExceptionAsync(() => processor.SearchAsync(request, CancellationToken.None));

            Assert.IsType<HttpRequestException>(ex);
            Assert.Equal("Response status code does not indicate success: 404 (Not Found).", ex.Message);
        }
        public void BodyContainsFilterFacet()
        {
            var expectedFilter = new ExistsCriteria("IsLocal");
            var expectedFacet = new FilterFacet("LocalSales", expectedFilter);
            var searchRequest = new SearchRequest { Facets = new List<IFacet>(new[] { expectedFacet }) };

            var formatter = new SearchRequestFormatter(defaultConnection, mapping, searchRequest);
            var body = JObject.Parse(formatter.Body);

            var result = body.TraverseWithAssert("facets", expectedFacet.Name, expectedFacet.Type, expectedFilter.Name, "field");
            Assert.Equal(expectedFilter.Field, result.ToString());
        }
        public static async Task ForcesBasicAuthorizationWhenProvidedWithUsernameAndPassword()
        {
            var messageHandler = new SpyMessageHandler();
            var localConnection = new ElasticConnection(messageHandler, new Uri("http://localhost"), "myUser", "myPass");
            var processor = new ElasticRequestProcessor(localConnection, mapping, log, retryPolicy);
            var request = new SearchRequest { DocumentType = "docType" };

            await processor.SearchAsync(request);

            var auth = messageHandler.Request.Headers.Authorization;
            Assert.NotNull(auth);
            Assert.Equal("Basic", auth.Scheme);
            Assert.Equal("myUser:myPass", Encoding.ASCII.GetString(Convert.FromBase64String(auth.Parameter)));
        }
        public void BodyContainsFilterFacetAndedWithRequestFilter()
        {
            var expectedFacet = new FilterFacet("LocalSales", new ExistsCriteria("IsLocal"));
            var searchRequest = new SearchRequest
            {
                Filter = new MissingCriteria("Country"),
                Query = new PrefixCriteria("Field", "Prefix"),
                Facets = new List<IFacet>(new[] { expectedFacet })
            };

            var formatter = new SearchRequestFormatter(defaultConnection, mapping, searchRequest);
            var body = JObject.Parse(formatter.Body);

            var andFilter = body.TraverseWithAssert("facets", expectedFacet.Name, expectedFacet.Type, "and");
            Assert.Equal(2, andFilter.Count());
        }
        public static async void SearchAsyncCapturesRequestInfoOnFailure()
        {
            var spyLog = new SpyLog();
            var brokenConnection = new ElasticConnection(new Uri("http://localhost:12"), index: "MyIndex");
            var processor = new ElasticRequestProcessor(brokenConnection, mapping, spyLog, new RetryPolicy(spyLog, 100, 1));
            var searchRequest = new SearchRequest { DocumentType = "docType" };
            var formatter = new SearchRequestFormatter(brokenConnection, mapping, searchRequest);

            var ex = await Record.ExceptionAsync(() => processor.SearchAsync(searchRequest, CancellationToken.None));

            Assert.IsType<RetryFailedException>(ex);
            var retryLogEntry = Assert.Single(spyLog.Entries, s => s.AdditionalInfo.ContainsKey("category") && s.AdditionalInfo["category"].Equals("retry"));
            Assert.Equal("MyIndex", retryLogEntry.AdditionalInfo["index"]);
            Assert.Equal(brokenConnection.GetSearchUri(searchRequest), retryLogEntry.AdditionalInfo["uri"]);
            Assert.Equal(formatter.Body, retryLogEntry.AdditionalInfo["query"]);
        }
        public void BodyContainsMultipleFacets()
        {
            var expectedFacets = new List<IFacet>
            {
                new FilterFacet("LocalSales", new ExistsCriteria("IsLocal")),
                new StatisticalFacet("TotalSales", "OrderTotal")
            };

            var searchRequest = new SearchRequest { Facets = expectedFacets };

            var formatter = new SearchRequestFormatter(defaultConnection, mapping, searchRequest);
            var body = JObject.Parse(formatter.Body);

            var facetResults = body.TraverseWithAssert("facets");
            foreach (var expectedFacet in expectedFacets)
                facetResults.TraverseWithAssert(expectedFacet.Name, expectedFacet.Type);
        }
        public void BodyContainsTermsFacet()
        {
            const int expectedSize = 1234;
            var expectedFacet = new TermsFacet("Totals", expectedSize, "OrderTotal", "OrderCost");
            var searchRequest = new SearchRequest { Facets = new List<IFacet>(new[] { expectedFacet }) };

            var formatter = new SearchRequestFormatter(defaultConnection, mapping, searchRequest);
            var body = JObject.Parse(formatter.Body);

            var result = body.TraverseWithAssert("facets", expectedFacet.Name, expectedFacet.Type);

            Assert.Equal(expectedSize.ToString(CultureInfo.InvariantCulture), result.TraverseWithAssert("size").ToString());

            var actualFields = result.TraverseWithAssert("fields").ToArray();
            foreach (var expectedField in expectedFacet.Fields)
                Assert.Contains(expectedField, actualFields);
        }
        public Task<ElasticResponse> SearchAsync(SearchRequest searchRequest, CancellationToken cancellationToken)
        {
            var formatter = new SearchRequestFormatter(connection, mapping, searchRequest);

            return retryPolicy.ExecuteAsync(
                async token => await connection.SearchAsync(
                    formatter.Body,
                    searchRequest,
                    token,
                    log),
                (response, exception) => !cancellationToken.IsCancellationRequested && exception != null,
                (response, additionalInfo) =>
                {
                    additionalInfo["index"] = connection.Index;
                    additionalInfo["uri"] = connection.GetSearchUri(searchRequest);
                    additionalInfo["query"] = formatter.Body;
                }, cancellationToken);
        }
		public override async Task<ElasticResponse> Search(
			string index,
			string document,
			string body,
			SearchRequest searchRequest,
			ILog log)
		{
			var response = await client.SearchAsync<string>(
							index,
							searchRequest.DocumentType,
							body,
							searchParams => SetRequestParameters(searchParams, searchRequest));

			if (!response.Success)
				throw new HttpRequestException("Response status code does not indicate success: 404 (Not Found).");

			return ParseResponse(response.Response, log);
		}
        public Task<ElasticResponse> SearchAsync(SearchRequest searchRequest, CancellationToken cancellationToken)
        {
            var formatter = new SearchRequestFormatter(connection, mapping, searchRequest);
            log.Debug(null, null, "Request: POST {0}", formatter.Uri);
            log.Debug(null, null, "Body:\n{0}", formatter.Body);

            return retryPolicy.ExecuteAsync(
                async token =>
                {
                    using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, formatter.Uri) { Content = new StringContent(formatter.Body) })
                    using (var response = await SendRequestAsync(connection.HttpClient, requestMessage, token))
                    using (var responseStream = await response.Content.ReadAsStreamAsync())
                        return ParseResponse(responseStream, log);
                },
                (response, exception) => !cancellationToken.IsCancellationRequested && exception != null,
                (response, additionalInfo) =>
                {
                    additionalInfo["index"] = connection.Index;
                    additionalInfo["uri"] = formatter.Uri;
                    additionalInfo["query"] = formatter.Body;
                }, cancellationToken);
        }
        /// <inheritdoc/>
        public override async Task<ElasticResponse> SearchAsync(
            string body,
            SearchRequest searchRequest,
            CancellationToken token,
            ILog log)
        {
            var uri = GetSearchUri(searchRequest);

            log.Debug(null, null, "Request: POST {0}", uri);
            log.Debug(null, null, "Body:\n{0}", body);

            using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, uri) { Content = new StringContent(body) })
            using (var response = await SendRequestAsync(requestMessage, token, log))
            using (var responseStream = await response.Content.ReadAsStreamAsync())
                return ParseResponse(responseStream, log);
        }
        public void BodyContainsTermsFacetWithDefaultSizeFromConnection()
        {
            const int expectedSize = 678;
            var sizedConnection = new ElasticConnection(defaultConnection.Endpoint, options:new ElasticConnectionOptions { SearchSizeDefault = expectedSize });
            var expectedFacet = new TermsFacet("Totals", null, "OrderTotal", "OrderCost");
            var searchRequest = new SearchRequest { Facets = new List<IFacet>(new[] { expectedFacet }) };

            var formatter = new SearchRequestFormatter(sizedConnection, mapping, searchRequest);
            var body = JObject.Parse(formatter.Body);

            var result = body.TraverseWithAssert("facets", expectedFacet.Name, expectedFacet.Type);

            Assert.Equal(expectedSize.ToString(CultureInfo.InvariantCulture), result.TraverseWithAssert("size").ToString());
        }
        public static async void NonSuccessfulHttpRequestThrows()
        {
            var messageHandler = new SpyMessageHandler();
            messageHandler.Response.StatusCode = HttpStatusCode.NotFound;
            var localConnection = new ElasticConnection(messageHandler, new Uri("http://localhost"), "myUser", "myPass");
            var request = new SearchRequest { DocumentType = "docType" };
            var formatter = new SearchRequestFormatter(localConnection, mapping, request);

            var ex = await Record.ExceptionAsync(() => localConnection.SearchAsync(
                formatter.Body,
                request,
                token,
                log));

            Assert.IsType<HttpRequestException>(ex);
            Assert.Equal("Response status code does not indicate success: 404 (Not Found).", ex.Message);
        }
        public static async Task LogsDebugMessagesDuringExecution()
        {
            var responseString = BuildResponseString(2, 1, 1, 0.3141, "testIndex", "testType", "testId");
            var messageHandler = new SpyMessageHandler();
            var spyLog = new SpyLog();
            messageHandler.Response.Content = new StringContent(responseString);
            var localConnection = new ElasticConnection(messageHandler, new Uri("http://localhost"), "myUser", "myPass", index: "SearchIndex");
            var request = new SearchRequest { DocumentType = "abc123", Size = 2112 };
            var formatter = new SearchRequestFormatter(localConnection, mapping, request);

            await localConnection.SearchAsync(
                formatter.Body,
                request,
                token,
                spyLog);

            Assert.Equal(4, spyLog.Entries.Count);
            Assert.Equal(@"Request: POST http://localhost/SearchIndex/abc123/_search", spyLog.Entries[0].Message);
            Assert.Equal(@"Body:" + '\n' + @"{""size"":2112,""timeout"":""10s""}", spyLog.Entries[1].Message);
            Assert.True(new Regex(@"Response: 200 OK \(in \d+ms\)").Match(spyLog.Entries[2].Message).Success);
            Assert.True(new Regex(@"Deserialized \d+ bytes into 1 hits in \d+ms").Match(spyLog.Entries[3].Message).Success);
        }
        public static async void SearchAsyncThrowsTaskCancelledExceptionWithAlreadyCancelledCancellationToken()
        {
            var spyLog = new SpyLog();
            var localConnection = new ElasticConnection(new Uri("http://localhost"), index: "SearchIndex");
            var request = new SearchRequest { DocumentType = "docType" };
            var formatter = new SearchRequestFormatter(localConnection, mapping, request);

            var ex = await Record.ExceptionAsync(() => localConnection.SearchAsync(
                formatter.Body,
                request,
                new CancellationToken(true),
                spyLog));

            Assert.IsType<TaskCanceledException>(ex);
        }
        public void BodyContainsTermsStatsFacet()
        {
            const int expectedSize = 101;
            var expectedFacet = new TermsStatsFacet("Name", "Key", "Value", expectedSize);
            var searchRequest = new SearchRequest { Facets = new List<IFacet>(new[] { expectedFacet }) };

            var formatter = new SearchRequestFormatter(defaultConnection, mapping, searchRequest);
            var body = JObject.Parse(formatter.Body);

            var result = body.TraverseWithAssert("facets", expectedFacet.Name, expectedFacet.Type);
            Assert.Equal(expectedFacet.Key, result.TraverseWithAssert("key_field").ToString());
            Assert.Equal(expectedFacet.Value, result.TraverseWithAssert("value_field").ToString());
            Assert.Equal(expectedSize.ToString(CultureInfo.InvariantCulture), result.TraverseWithAssert("size").ToString());
        }
        public static async Task LogsDebugMessagesDuringExecution()
        {
            var responseString = BuildResponseString(2, 1, 1, 0.3141, "testIndex", "testType", "testId");
            var messageHandler = new SpyMessageHandler();
            var log = new SpyLog();
            messageHandler.Response.Content = new StringContent(responseString);
            var localConnection = new ElasticConnection(messageHandler, new Uri("http://localhost"), "myUser", "myPass", index: "SearchIndex");
            var processor = new ElasticRequestProcessor(localConnection, mapping, log, retryPolicy);
            var request = new SearchRequest { DocumentType = "abc123", Size = 2112 };

            await processor.SearchAsync(request);

            Assert.Equal(4, log.Messages.Count);
            Assert.Equal(@"[VERBOSE] Request: POST http://localhost/SearchIndex/abc123/_search", log.Messages[0]);
            Assert.Equal(@"[VERBOSE] Body:" +'\n' + @"{""size"":2112,""timeout"":""10s""}", log.Messages[1]);
            Assert.True(new Regex(@"\[VERBOSE\] Response: 200 OK \(in \d+ms\)").Match(log.Messages[2]).Success);
            Assert.True(new Regex(@"\[VERBOSE\] Deserialized \d+ bytes into 1 hits in \d+ms").Match(log.Messages[3]).Success);
        }
        /// <inheritdoc/>
        public override Uri GetSearchUri(SearchRequest searchRequest)
        {
            var builder = new UriBuilder(endpoint);
            builder.Path += (Index ?? "_all") + "/";

            if (!String.IsNullOrEmpty(searchRequest.DocumentType))
                builder.Path += searchRequest.DocumentType + "/";

            builder.Path += "_search";

            var parameters = builder.Uri.GetComponents(UriComponents.Query, UriFormat.Unescaped)
                .Split(parameterSeparator, StringSplitOptions.RemoveEmptyEntries)
                .Select(p => p.Split('='))
                .ToDictionary(k => k[0], v => v.Length > 1 ? v[1] : null);

            if (!String.IsNullOrEmpty(searchRequest.SearchType))
                parameters["search_type"] = searchRequest.SearchType;

            if (Options.Pretty)
                parameters["pretty"] = "true";

            builder.Query = String.Join("&", parameters.Select(p => p.Value == null ? p.Key : p.Key + "=" + p.Value));

            return builder.Uri;
        }
        public static async Task NoAuthorizationWithEmptyUserName()
        {
            var messageHandler = new SpyMessageHandler();
            var localConnection = new ElasticConnection(messageHandler, new Uri("http://localhost"));
            var request = new SearchRequest { DocumentType = "docType" };
            var formatter = new SearchRequestFormatter(localConnection, mapping, request);

            await localConnection.SearchAsync(
                formatter.Body,
                request,
                token,
                log);

            Assert.Null(messageHandler.Request.Headers.Authorization);
        }