Skip to content

Commit aaec1a0

Browse files
Add accessor properties for dictionary responses (#7066) (#7068)
Co-authored-by: Steve Gordon <[email protected]>
1 parent 9759c5a commit aaec1a0

File tree

11 files changed

+286
-3
lines changed

11 files changed

+286
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using System.Text.Json.Serialization;
7+
8+
namespace Elastic.Clients.Elasticsearch.IndexManagement;
9+
10+
public partial class GetAliasResponse
11+
{
12+
/// <summary>
13+
/// A dictionary containing the <see cref="IndexAliases"/> organised by <see cref="IndexName"/>.
14+
/// </summary>
15+
[JsonIgnore]
16+
public IReadOnlyDictionary<IndexName, IndexAliases> Aliases => BackingDictionary;
17+
18+
/// <summary>
19+
/// Checks if a response is functionally valid or not.
20+
/// This is a client abstraction to have a single property to check whether there was something wrong with a request.
21+
/// <para>
22+
/// The aliases endpoint returns a 404 when some of the specified alias names for an index cannot be found. For such partial responses,
23+
/// the client considers the response to be valid.
24+
/// </para>
25+
/// </summary>
26+
public override bool IsValidResponse => base.IsValidResponse || Aliases.Count > 0;
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using System.Text.Json.Serialization;
7+
8+
namespace Elastic.Clients.Elasticsearch.IndexManagement;
9+
10+
public partial class GetFieldMappingResponse
11+
{
12+
[JsonIgnore]
13+
public IReadOnlyDictionary<IndexName, TypeFieldMappings> FieldMappings => BackingDictionary;
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using System.Text.Json.Serialization;
7+
8+
namespace Elastic.Clients.Elasticsearch.IndexManagement;
9+
10+
public partial class GetIndexResponse
11+
{
12+
[JsonIgnore]
13+
public IReadOnlyDictionary<IndexName, IndexState> Indices => BackingDictionary;
14+
}

src/Elastic.Clients.Elasticsearch/Api/IndexManagement/GetMappingResponse.cs

+2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Collections.Generic;
6+
using System.Text.Json.Serialization;
67
using Elastic.Clients.Elasticsearch.Mapping;
78

89
namespace Elastic.Clients.Elasticsearch.IndexManagement;
910

1011
public partial class GetMappingResponse
1112
{
13+
[JsonIgnore]
1214
public IReadOnlyDictionary<IndexName, IndexMappingRecord> Indices => BackingDictionary;
1315
}
1416

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using System.Text.Json.Serialization;
7+
8+
namespace Elastic.Clients.Elasticsearch.IndexManagement;
9+
10+
public partial class GetTemplateResponse
11+
{
12+
[JsonIgnore]
13+
public IReadOnlyDictionary<string, TemplateMapping> TemplateMappings => BackingDictionary;
14+
}

src/Elastic.Clients.Elasticsearch/Client/IndicesNamespace.cs

+17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System.Threading.Tasks;
66
using System.Threading;
7+
using System;
78

89
namespace Elastic.Clients.Elasticsearch.IndexManagement;
910

@@ -35,4 +36,20 @@ public Task<RefreshResponse> RefreshAsync(Indices indices, CancellationToken can
3536
request.BeforeRequest();
3637
return DoRequestAsync<RefreshRequest, RefreshResponse, RefreshRequestParameters>(request, cancellationToken);
3738
}
39+
40+
public virtual GetAliasResponse GetAlias(Indices indicies, Action<GetAliasRequestDescriptor> configureRequest)
41+
{
42+
var descriptor = new GetAliasRequestDescriptor(indicies);
43+
configureRequest?.Invoke(descriptor);
44+
descriptor.BeforeRequest();
45+
return DoRequest<GetAliasRequestDescriptor, GetAliasResponse, GetAliasRequestParameters>(descriptor);
46+
}
47+
48+
public virtual Task<GetAliasResponse> GetAliasAsync(Indices indicies, Action<GetAliasRequestDescriptor> configureRequest, CancellationToken cancellationToken = default)
49+
{
50+
var descriptor = new GetAliasRequestDescriptor(indicies);
51+
configureRequest?.Invoke(descriptor);
52+
descriptor.BeforeRequest();
53+
return DoRequestAsync<GetAliasRequestDescriptor, GetAliasResponse, GetAliasRequestParameters>(descriptor, cancellationToken);
54+
}
3855
}

tests/Tests.Configuration/tests.default.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
mode: u
99
# the elasticsearch version that should be started
1010
# Can be a snapshot version of sonatype or "latest" to get the latest snapshot of sonatype
11-
elasticsearch_version: 8.4.3
11+
elasticsearch_version: 8.5.2
1212
# cluster filter allows you to only run the integration tests of a particular cluster (cluster suffix not needed)
1313
# cluster_filter:
1414
# whether we want to forcefully reseed on the node, if you are starting the tests with a node already running

tests/Tests.Core/ManagedElasticsearch/NodeSeeders/DefaultSeeder.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ public async Task CreateIndicesAsync()
240240
{
241241
aliases = new Dictionary<string, object>
242242
{
243-
{ "projects-alias", new { } },
244-
{ "projects-only", new { filter = new { term = new { join = new { value = "project" }}}} }
243+
{ ProjectsAliasName, new { } },
244+
{ ProjectsAliasFilter, new { filter = new { term = new { join = new { value = "project" }}}} }
245245
},
246246
mappings = new
247247
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using Elastic.Clients.Elasticsearch.IndexManagement;
7+
using Tests.Core.ManagedElasticsearch.Clusters;
8+
using Tests.Core.ManagedElasticsearch.NodeSeeders;
9+
using Tests.Domain;
10+
using Tests.Framework.EndpointTests;
11+
using Tests.Framework.EndpointTests.TestState;
12+
13+
namespace Tests.IndexManagement.AliasManagement;
14+
15+
public class GetAliasApiTests : ApiIntegrationTestBase<ReadOnlyCluster, GetAliasResponse, GetAliasRequestDescriptor, GetAliasRequest>
16+
{
17+
private static readonly Names Names = Infer.Names(DefaultSeeder.ProjectsAliasName);
18+
19+
public GetAliasApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
20+
21+
protected override bool ExpectIsValid => true;
22+
protected override int ExpectStatusCode => 200;
23+
protected override HttpMethod ExpectHttpMethod => HttpMethod.GET;
24+
protected override bool SupportsDeserialization => false;
25+
protected override string ExpectedUrlPathAndQuery => $"_all/_alias/{DefaultSeeder.ProjectsAliasName}";
26+
27+
protected override GetAliasRequest Initializer => new(Indices.All, Names);
28+
protected override Action<GetAliasRequestDescriptor> Fluent => d => d.Name(Names);
29+
30+
protected override LazyResponses ClientUsage() => Calls(
31+
(client, f) => client.Indices.GetAlias(Indices.All, f),
32+
(client, f) => client.Indices.GetAliasAsync(Indices.All, f),
33+
(client, r) => client.Indices.GetAlias(r),
34+
(client, r) => client.Indices.GetAliasAsync(r)
35+
);
36+
37+
protected override void ExpectResponse(GetAliasResponse response)
38+
{
39+
response.Aliases.Should().NotBeEmpty($"expect to find indices pointing to {DefaultSeeder.ProjectsAliasName}");
40+
var indexAliases = response.Aliases[Infer.Index<Project>()];
41+
indexAliases.Should().NotBeNull("expect to find alias for project");
42+
indexAliases.Aliases.Should().NotBeEmpty("expect to find aliases dictionary definitions for project");
43+
var alias = indexAliases.Aliases[DefaultSeeder.ProjectsAliasName];
44+
alias.Should().NotBeNull();
45+
}
46+
}
47+
48+
// TODO: Support exception information from specification and avoid default error response deserialization in transport
49+
50+
//public class GetAliasPartialMatchApiTests : ApiIntegrationTestBase<ReadOnlyCluster, GetAliasResponse, GetAliasRequestDescriptor, GetAliasRequest>
51+
//{
52+
// private static readonly Names Names = Infer.Names(DefaultSeeder.ProjectsAliasName, "x", "y");
53+
54+
// public GetAliasPartialMatchApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
55+
56+
// protected override bool ExpectIsValid => true;
57+
// protected override int ExpectStatusCode => 404;
58+
// protected override HttpMethod ExpectHttpMethod => HttpMethod.GET;
59+
// protected override bool SupportsDeserialization => false;
60+
// protected override string ExpectedUrlPathAndQuery => $"_all/_alias/{DefaultSeeder.ProjectsAliasName}%2Cx%2Cy";
61+
62+
// protected override GetAliasRequest Initializer => new(Indices.All, Names);
63+
// protected override Action<GetAliasRequestDescriptor> Fluent => d => d.Name(Names);
64+
65+
// protected override LazyResponses ClientUsage() => Calls(
66+
// (client, f) => client.Indices.GetAlias(Indices.All, f),
67+
// (client, f) => client.Indices.GetAliasAsync(Indices.All, f),
68+
// (client, r) => client.Indices.GetAlias(r),
69+
// (client, r) => client.Indices.GetAliasAsync(r)
70+
// );
71+
72+
// protected override void ExpectResponse(GetAliasResponse response)
73+
// {
74+
// response.Aliases.Should().NotBeNull();
75+
// response.Aliases.Count.Should().BeGreaterThan(0);
76+
// }
77+
//}
78+
79+
public class GetAliasNotFoundApiTests : ApiIntegrationTestBase<ReadOnlyCluster, GetAliasResponse, GetAliasRequestDescriptor, GetAliasRequest>
80+
{
81+
private static readonly Names Names = Infer.Names("bad-alias");
82+
83+
public GetAliasNotFoundApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
84+
85+
protected override bool ExpectIsValid => false;
86+
protected override int ExpectStatusCode => 404;
87+
protected override HttpMethod ExpectHttpMethod => HttpMethod.GET;
88+
protected override bool SupportsDeserialization => false;
89+
protected override string ExpectedUrlPathAndQuery => $"_all/_alias/bad-alias";
90+
91+
protected override GetAliasRequest Initializer => new(Indices.All, Names);
92+
protected override Action<GetAliasRequestDescriptor> Fluent => d => d.Name(Names);
93+
94+
protected override LazyResponses ClientUsage() => Calls(
95+
(client, f) => client.Indices.GetAlias(Indices.All, f),
96+
(client, f) => client.Indices.GetAliasAsync(Indices.All, f),
97+
(client, r) => client.Indices.GetAlias(r),
98+
(client, r) => client.Indices.GetAliasAsync(r)
99+
);
100+
101+
protected override void ExpectResponse(GetAliasResponse response)
102+
{
103+
response.ElasticsearchServerError.Should().NotBeNull();
104+
response.ElasticsearchServerError.Error.Reason.Should().Contain("missing");
105+
106+
response.Aliases.Should().NotBeNull();
107+
response.Aliases.Should().BeEmpty();
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using Elastic.Clients.Elasticsearch.IndexManagement;
7+
using Elastic.Clients.Elasticsearch.Mapping;
8+
using Tests.Core.ManagedElasticsearch.Clusters;
9+
using Tests.Domain;
10+
using Tests.Framework.EndpointTests;
11+
using Tests.Core.Extensions;
12+
using Tests.Framework.EndpointTests.TestState;
13+
14+
namespace Tests.IndexManagement.MappingManagement;
15+
16+
public class GetFieldMappingApiTests : ApiIntegrationTestBase<ReadOnlyCluster, GetFieldMappingResponse, GetFieldMappingRequestDescriptor<Project>, GetFieldMappingRequest>
17+
{
18+
// TODO: Introduce percolation assertions once seeded
19+
20+
private static readonly Fields Fields = Infer.Fields<Project>(p => p.Name, p => p.LeadDeveloper.IpAddress);
21+
private static readonly Field NameField = Infer.Field<Project>(p => p.Name);
22+
private static readonly Indices OnIndices = Infer.Index<Project>()/*.And<ProjectPercolation>()*/;
23+
24+
public GetFieldMappingApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
25+
26+
protected override bool ExpectIsValid => true;
27+
protected override int ExpectStatusCode => 200;
28+
protected override HttpMethod ExpectHttpMethod => HttpMethod.GET;
29+
protected override GetFieldMappingRequest Initializer => new(OnIndices, Fields);
30+
protected override Action<GetFieldMappingRequestDescriptor<Project>> Fluent => d => d.Indices(OnIndices);
31+
32+
protected override string ExpectedUrlPathAndQuery => "/project/_mapping/field/name%2CleadDeveloper.ipAddress";
33+
34+
protected override LazyResponses ClientUsage() => Calls(
35+
(client, f) => client.Indices.GetFieldMapping(Fields, f),
36+
(client, f) => client.Indices.GetFieldMappingAsync(Fields, f),
37+
(client, r) => client.Indices.GetFieldMapping(r),
38+
(client, r) => client.Indices.GetFieldMappingAsync(r)
39+
);
40+
41+
protected override GetFieldMappingRequestDescriptor<Project> NewDescriptor() => new(Fields);
42+
43+
protected override void ExpectResponse(GetFieldMappingResponse response)
44+
{
45+
response.FieldMappings.Should()
46+
.NotBeEmpty("expect indices on the response")
47+
.And.ContainKey(Infer.Index<Project>(), "expect project to return field mappings");
48+
//.And.ContainKey(Infer.Index<ProjectPercolation>(), "expect project percolation to return field mappings");
49+
50+
var projectMappings = response.FieldMappings[Infer.Index<Project>()];
51+
projectMappings.Should().NotBeNull("project mapping value in the dictionary should not point to a null value");
52+
53+
// TODO - Reintroduce this once GetFieldMappingResponse.FieldMappings uses ResolvableReadOnlyDictionaryConverter
54+
//projectMappings.Mappings.Should()
55+
// .NotBeEmpty("project has fields so should contain a type mapping")
56+
// .And.ContainKey(NameField, "project mappings should have 'name'");
57+
58+
//var fieldTypeMapping = projectMappings.Mappings[NameField];
59+
//fieldTypeMapping.Should().NotBeNull("name field mapping should exist");
60+
//fieldTypeMapping.FullName.Should().NotBeNullOrEmpty();
61+
//fieldTypeMapping.Mapping.Should()
62+
// .NotBeEmpty("field type mapping should return a `mapping` with the field information")
63+
// .And.HaveCount(1, "field type mappings only return information from a single field")
64+
// .And.ContainKey(NameField);
65+
66+
//var fieldMapping = fieldTypeMapping.Mapping[NameField];
67+
//AssertNameFieldMapping(fieldMapping);
68+
69+
//fieldMapping = response.MappingFor<Project>(NameField);
70+
//AssertNameFieldMapping(fieldMapping);
71+
72+
//fieldMapping = response.MappingFor<Project>(p => p.Name);
73+
//AssertNameFieldMapping(fieldMapping);
74+
}
75+
76+
private static void AssertNameFieldMapping(FieldMapping fieldMapping)
77+
{
78+
fieldMapping.Should().NotBeNull("expected to find name on field type mapping for project");
79+
80+
//var nameKeyword = fieldMapping as KeywordProperty;
81+
//nameKeyword.Should().NotBeNull("the field type is a keyword mapping");
82+
//nameKeyword.Store.Should().BeTrue("name is keyword field that has store enabled");
83+
//nameKeyword.Fields.Should().NotBeEmpty().And.HaveCount(2);
84+
//nameKeyword.Fields["standard"].Should().NotBeNull();
85+
}
86+
}

0 commit comments

Comments
 (0)