Skip to content

Commit 3a63165

Browse files
authored
Merge branch 'main' into feat/ingestion-tasks-v2
2 parents ff40c85 + f643292 commit 3a63165

File tree

42 files changed

+596
-196
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+596
-196
lines changed

.github/workflows/check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ jobs:
271271
path: clients-javascript.zip
272272

273273
client_gen:
274-
timeout-minutes: 20
274+
timeout-minutes: 10
275275
runs-on: ubuntu-22.04
276276
needs:
277277
- setup

clients/algoliasearch-client-csharp/algoliasearch/Http/AlgoliaHttpRequester.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public async Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpa
105105
_logger.LogWarning(ex, "Timeout while sending request");
106106
}
107107

108-
return new AlgoliaHttpResponse { IsTimedOut = true, Error = ex.ToString() };
108+
return new AlgoliaHttpResponse { IsTimedOut = true, Error = ex.Message };
109109
}
110110
catch (HttpRequestException ex)
111111
{

clients/algoliasearch-client-swift/Sources/Core/Networking/HTTP/AlgoliaError.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Foundation
1010
public enum AlgoliaError: Error, LocalizedError {
1111
case requestError(Error)
1212
case httpError(HTTPError)
13-
case noReachableHosts(intermediateErrors: [Error])
13+
case noReachableHosts(intermediateErrors: [Error], exposeIntermediateErrors: Bool)
1414
case missingData
1515
case decodingFailure(Error)
1616
case runtimeError(String)
@@ -24,8 +24,13 @@ public enum AlgoliaError: Error, LocalizedError {
2424
"Request failed: \(error.localizedDescription)"
2525
case let .httpError(error):
2626
"HTTP error: \(error)"
27-
case let .noReachableHosts(errors):
28-
"All hosts are unreachable. Intermediate errors: \(errors.map(\.localizedDescription).joined(separator: ", "))"
27+
case let .noReachableHosts(errors, exposeIntermediateErrors):
28+
"All hosts are unreachable. " +
29+
(
30+
exposeIntermediateErrors ?
31+
"Intermediate errors:\n- \(errors.map(\.localizedDescription).joined(separator: "\n- "))" :
32+
"You can use 'exposeIntermediateErrors: true' in the config to investigate."
33+
)
2934
case .missingData:
3035
"Missing response data"
3136
case .decodingFailure:

clients/algoliasearch-client-swift/Sources/Core/Networking/RetryStrategy/AlgoliaRetryStrategy.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,15 @@ class AlgoliaRetryStrategy: RetryStrategy {
4141
self.hosts.resetAll(for: callType)
4242
}
4343

44+
var hostIterator = self.hosts
45+
.sorted { $0.lastUpdated.compare($1.lastUpdated) == .orderedAscending }
46+
.filter { $0.supports(callType) && $0.isUp }
47+
.makeIterator()
48+
4449
return HostIterator { [weak self] in
4550
guard let retryStrategy = self else { return nil }
4651
return retryStrategy.queue.sync {
47-
retryStrategy.hosts
48-
.sorted { $0.lastUpdated.compare($1.lastUpdated) == .orderedAscending }
49-
.first { $0.supports(callType) && $0.isUp }
52+
hostIterator.next()
5053
}
5154
}
5255
}

clients/algoliasearch-client-swift/Sources/Core/Networking/Transporter.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ open class Transporter {
1515
let configuration: BaseConfiguration
1616
let retryStrategy: RetryStrategy
1717
let requestBuilder: RequestBuilder
18+
let exposeIntermediateErrors: Bool
1819

1920
public init(
2021
configuration: BaseConfiguration,
2122
retryStrategy: RetryStrategy? = nil,
22-
requestBuilder: RequestBuilder? = nil
23+
requestBuilder: RequestBuilder? = nil,
24+
exposeIntermediateErrors: Bool = false
2325
) {
2426
self.configuration = configuration
2527
self.retryStrategy = retryStrategy ?? AlgoliaRetryStrategy(configuration: configuration)
28+
self.exposeIntermediateErrors = exposeIntermediateErrors
2629

2730
guard let requestBuilder else {
2831
let sessionConfiguration: URLSessionConfiguration = .default
@@ -139,6 +142,9 @@ open class Transporter {
139142
}
140143
}
141144

142-
throw AlgoliaError.noReachableHosts(intermediateErrors: intermediateErrors)
145+
throw AlgoliaError.noReachableHosts(
146+
intermediateErrors: intermediateErrors,
147+
exposeIntermediateErrors: self.exposeIntermediateErrors
148+
)
143149
}
144150
}

generators/src/main/java/com/algolia/codegen/cts/tests/ClientTestData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ class Step {
2222
class Expected {
2323

2424
public String type;
25-
public String error;
25+
public Object error;
2626
public Object match;
2727
}

generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,17 @@ public void run(Map<String, CodegenModel> models, Map<String, CodegenOperation>
163163
}
164164
if (step.expected.error != null) {
165165
stepOut.put("isError", true);
166-
stepOut.put("expectedError", step.expected.error);
166+
if (step.expected.error instanceof Map errorMap) {
167+
stepOut.put("expectedError", errorMap.getOrDefault(language, "<missing error for " + language + ">"));
168+
} else {
169+
stepOut.put("expectedError", step.expected.error);
170+
}
167171
if (language.equals("go") && step.method != null) {
168172
// hack for go that use PascalCase, but just in the operationID
169-
stepOut.put("expectedError", step.expected.error.replace(step.method, Helpers.toPascalCase(step.method)));
173+
stepOut.put(
174+
"expectedError",
175+
((String) stepOut.get("expectedError")).replace(step.method, Helpers.toPascalCase(step.method))
176+
);
170177
}
171178
} else if (step.expected.match != null) {
172179
Map<String, Object> matchMap = new HashMap<>();

scripts/cli/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,12 @@ ctsCommand
160160
.description('Start the test servers in standalone mode')
161161
.action(async () => {
162162
setVerbose(true);
163-
await startTestServer();
163+
await startTestServer({
164+
benchmark: true,
165+
client: true,
166+
requests: true,
167+
e2e: true,
168+
});
164169
});
165170

166171
program

scripts/cts/runCts.ts

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getTestOutputFolder } from '../config.js';
55
import { createSpinner } from '../spinners.js';
66
import type { Language } from '../types.js';
77

8-
import { startBenchmarkServer, startTestServer } from './testServer';
8+
import { startTestServer } from './testServer';
99
import { printBenchmarkReport } from './testServer/benchmark.js';
1010
import { assertChunkWrapperValid } from './testServer/chunkWrapper.js';
1111
import { assertValidReplaceAllObjects } from './testServer/replaceAllObjects.js';
@@ -142,38 +142,30 @@ export async function runCts(
142142
clients: string[],
143143
suites: Record<CTSType, boolean>,
144144
): Promise<void> {
145-
const useTestServer = suites.client && (clients.includes('search') || clients.includes('all'));
146-
const useBenchmarkServer =
145+
const withBenchmarkServer =
147146
suites.benchmark && (clients.includes('search') || clients.includes('all'));
148-
149-
let closeTestServer: () => Promise<void> = async () => {};
150-
if (useTestServer) {
151-
closeTestServer = await startTestServer();
152-
}
153-
154-
let closeBenchmarkServer: () => Promise<void> = async () => {};
155-
if (useBenchmarkServer) {
156-
closeBenchmarkServer = await startBenchmarkServer();
157-
}
147+
const withClientServer = suites.client && (clients.includes('search') || clients.includes('all'));
148+
const closeTestServer = await startTestServer({
149+
...suites,
150+
benchmark: withBenchmarkServer,
151+
client: withClientServer,
152+
});
158153

159154
for (const lang of languages) {
160155
await runCtsOne(lang, suites);
161156
}
162157

163-
if (useTestServer) {
164-
await closeTestServer();
158+
await closeTestServer();
165159

160+
if (withClientServer) {
166161
const skip = (lang: Language): number => (languages.includes(lang) ? 1 : 0);
167162

168163
assertValidTimeouts(languages.length);
169164
assertChunkWrapperValid(languages.length - skip('dart') - skip('scala'));
170165
assertValidReplaceAllObjects(languages.length - skip('dart') - skip('scala'));
171166
assertValidWaitForApiKey(languages.length - skip('dart') - skip('scala'));
172167
}
173-
174-
if (useBenchmarkServer) {
175-
await closeBenchmarkServer();
176-
168+
if (withBenchmarkServer) {
177169
printBenchmarkReport();
178170
}
179171
}

scripts/cts/testServer/index.ts

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import express from 'express';
44
import type { Express } from 'express';
55

66
import { createSpinner } from '../../spinners';
7+
import type { CTSType } from '../runCts';
78

89
import { benchmarkServer } from './benchmark';
910
import { chunkWrapperServer } from './chunkWrapper';
@@ -13,15 +14,28 @@ import { timeoutServer } from './timeout';
1314
import { timeoutServerBis } from './timeoutBis';
1415
import { waitForApiKeyServer } from './waitForApiKey';
1516

16-
export async function startTestServer(): Promise<() => Promise<void>> {
17-
const servers = await Promise.all([
18-
timeoutServer(),
19-
gzipServer(),
20-
timeoutServerBis(),
21-
replaceAllObjectsServer(),
22-
chunkWrapperServer(),
23-
waitForApiKeyServer(),
24-
]);
17+
export async function startTestServer(
18+
suites: Record<CTSType, boolean>,
19+
): Promise<() => Promise<void>> {
20+
const toStart: Array<Promise<Server>> = [];
21+
if (suites.client) {
22+
toStart.push(
23+
timeoutServer(),
24+
gzipServer(),
25+
timeoutServerBis(),
26+
replaceAllObjectsServer(),
27+
chunkWrapperServer(),
28+
waitForApiKeyServer(),
29+
);
30+
}
31+
if (suites.benchmark) {
32+
toStart.push(benchmarkServer());
33+
}
34+
if (toStart.length === 0) {
35+
return async () => {};
36+
}
37+
38+
const servers = await Promise.all(toStart);
2539

2640
return async () => {
2741
await Promise.all(
@@ -35,16 +49,6 @@ export async function startTestServer(): Promise<() => Promise<void>> {
3549
};
3650
}
3751

38-
export async function startBenchmarkServer(): Promise<() => Promise<void>> {
39-
const server = await benchmarkServer();
40-
41-
return async () => {
42-
await new Promise<void>((resolve) => {
43-
server.close(() => resolve());
44-
});
45-
};
46-
}
47-
4852
export async function setupServer(
4953
name: string,
5054
port: number,

scripts/cts/testServer/timeout.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import type express from 'express';
55

66
import { setupServer } from '.';
77

8-
const timeoutState: Record<string, { timestamp: number[]; duration: number[] }> = {};
8+
const timeoutState: Record<string, { timestamp: number[]; duration: number[]; hangCount: number }> =
9+
{};
910

1011
export function assertValidTimeouts(expectedCount: number): void {
1112
// assert that the retry strategy uses the correct timings, by checking the time between each request, and how long each request took before being timed out
@@ -15,6 +16,7 @@ export function assertValidTimeouts(expectedCount: number): void {
1516
}
1617

1718
for (const [lang, state] of Object.entries(timeoutState)) {
19+
expect(state.hangCount).to.equal(1);
1820
expect(state.timestamp.length).to.equal(3);
1921
expect(state.duration.length).to.equal(3);
2022
expect(state.timestamp[1] - state.timestamp[0]).to.be.closeTo(state.duration[0], 100);
@@ -23,7 +25,7 @@ export function assertValidTimeouts(expectedCount: number): void {
2325
// languages are not consistent yet for the delay between requests
2426
switch (lang) {
2527
case 'javascript':
26-
expect(state.duration[0] * 4).to.be.closeTo(state.duration[1], 200);
28+
expect(state.duration[0] * 4).to.be.closeTo(state.duration[1], 300);
2729
break;
2830
case 'php':
2931
expect(state.duration[0] * 2).to.be.closeTo(state.duration[1], 200);
@@ -51,6 +53,7 @@ export function retryHandler(after: number, message: string): express.RequestHan
5153
timeoutState[lang] = {
5254
timestamp: [],
5355
duration: [],
56+
hangCount: 0,
5457
};
5558
}
5659

@@ -70,6 +73,21 @@ export function retryHandler(after: number, message: string): express.RequestHan
7073
function addRoutes(app: express.Express): void {
7174
// this endpoint is also defined in the gzip server but without the timeout
7275
app.get('/1/test/retry/:lang', retryHandler(20000, 'timeout test server response'));
76+
77+
app.get('/1/test/hang/:lang', (req) => {
78+
const lang = req.params.lang;
79+
if (!timeoutState[lang]) {
80+
timeoutState[lang] = {
81+
timestamp: [],
82+
duration: [],
83+
hangCount: 0,
84+
};
85+
}
86+
87+
timeoutState[lang].hangCount++;
88+
89+
// no response, just hang
90+
});
7391
}
7492

7593
export function timeoutServer(): Promise<Server> {

templates/dart/tests/client/client.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ void main() {
2222
{{/autoCreateClient}}
2323
{{#steps}}
2424
{{#isError}}
25-
expectError(
25+
await expectError(
2626
'{{{expectedError}}}',
2727
() async {
2828
{{#dynamicTemplate}}{{/dynamicTemplate}}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{^useEchoRequester}}{{^isBenchmark}}res, err := {{/isBenchmark}}{{#isBenchmark}}_, err = {{/isBenchmark}}{{/useEchoRequester}}{{#useEchoRequester}}_, err = {{/useEchoRequester}}{{> tests/method}}
1+
res, err = {{> tests/method}}

templates/go/tests/client/tests.mustache

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// {{{testName}}}
33
func Test{{#lambda.titlecase}}{{clientPrefix}}{{testType}}{{/lambda.titlecase}}{{testIndex}}(t *testing.T) {
44
var err error
5+
var res any
6+
_ = res
57
{{#autoCreateClient}}
68
client, echo := create{{#lambda.titlecase}}{{clientPrefix}}{{/lambda.titlecase}}Client(t)
79
{{/autoCreateClient}}

templates/swift/tests/Package.mustache

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ let package = Package(
3636
.testTarget(
3737
name: "requests",
3838
dependencies: [
39-
.product(name: "DotEnv", package: "DotEnv"),
4039
.target(name: "Utils"),
4140
] + libraries
4241
),
@@ -50,7 +49,6 @@ let package = Package(
5049
.testTarget(
5150
name: "benchmark",
5251
dependencies: [
53-
.product(name: "DotEnv", package: "DotEnv"),
5452
.target(name: "Utils"),
5553
] + libraries
5654
)

tests/CTS/client/search/api.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,47 @@
8787
}
8888
]
8989
},
90+
{
91+
"testName": "tests the retry strategy error",
92+
"autoCreateClient": false,
93+
"steps": [
94+
{
95+
"type": "createClient",
96+
"parameters": {
97+
"appId": "test-app-id",
98+
"apiKey": "test-api-key",
99+
"customHosts": [
100+
{
101+
"host": "${{localhost}}",
102+
"port": 6676
103+
}
104+
]
105+
}
106+
},
107+
{
108+
"type": "method",
109+
"method": "customGet",
110+
"parameters": {
111+
"path": "1/test/hang/${{language}}"
112+
},
113+
"expected": {
114+
"error": {
115+
"csharp": "RetryStrategy failed to connect to Algolia. Reason: The operation has timed out.",
116+
"dart": "UnreachableHostsException{errors: [AlgoliaTimeoutException{error: DioException [receive timeout]: The request took longer than 0:00:05.000000 to receive data. It was aborted. To get rid of this exception, try raising the RequestOptions.receiveTimeout above the duration of 0:00:05.000000 or improve the response time of the server.}]}",
117+
"go": "failed to do request: all hosts have been contacted unsuccessfully, it can either be a server or a network error or wrong appID/key credentials were used. You can use 'ExposeIntermediateNetworkErrors: true' in the config to investigate.",
118+
"java": "Error(s) while processing the retry strategy\\nCaused by: java.net.SocketTimeoutException: timeout",
119+
"javascript": "Unreachable hosts - your application id may be incorrect. If the error persists, please reach out to the Algolia Support team: https://alg.li/support.",
120+
"kotlin": "Error(s) while processing the retry strategy",
121+
"php": "Impossible to connect, please check your Algolia Application Id.",
122+
"python": "Unreachable hosts",
123+
"ruby": "Unreachable hosts. Last error for ${{localhost}}: Net::ReadTimeout with #<TCPSocket:(closed)>",
124+
"scala": "Error(s) while processing the retry strategy",
125+
"swift": "All hosts are unreachable. You can use 'exposeIntermediateErrors: true' in the config to investigate."
126+
}
127+
}
128+
}
129+
]
130+
},
90131
{
91132
"testName": "test the compression strategy",
92133
"autoCreateClient": false,

0 commit comments

Comments
 (0)