Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat(clients): add api key helper test #3338

Merged
merged 20 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,15 @@ public GetTaskResponse WaitForAppTask(long taskId, int maxRetries = DefaultMaxRe
/// <summary>
/// Helper method that waits for an API key task to be processed.
/// </summary>
/// <param name="operation">The `operation` that was done on a `key`.</param>
/// <param name="key">The key that has been added, deleted or updated.</param>
/// <param name="operation">The `operation` that was done on a `key`.</param>
/// <param name="apiKey">Necessary to know if an `update` operation has been processed, compare fields of the response with it. (optional - mandatory if operation is UPDATE)</param>
/// <param name="maxRetries">The maximum number of retry. 50 by default. (optional)</param>
/// <param name="timeout">The function to decide how long to wait between retries. Math.Min(retryCount * 200, 5000) by default. (optional)</param>
/// <param name="requestOptions">The requestOptions to send along with the query, they will be merged with the transporter requestOptions. (optional)</param>
/// <param name="ct">Cancellation token (optional)</param>
public async Task<GetApiKeyResponse> WaitForApiKeyAsync(ApiKeyOperation operation, string key,
public async Task<GetApiKeyResponse> WaitForApiKeyAsync(string key,
ApiKeyOperation operation,
ApiKey apiKey = default, int maxRetries = DefaultMaxRetries, Func<int, int> timeout = null,
RequestOptions requestOptions = null, CancellationToken ct = default)
{
Expand Down Expand Up @@ -112,36 +113,35 @@ public async Task<GetApiKeyResponse> WaitForApiKeyAsync(ApiKeyOperation operatio
}, maxRetries, timeout, ct).ConfigureAwait(false);
}

var addedKey = new GetApiKeyResponse();

// check the status of the getApiKey method
await RetryUntil(async () =>
return await RetryUntil(async () =>
{
try
{
addedKey = await GetApiKeyAsync(key, requestOptions, ct).ConfigureAwait(false);
// magic number to signify we found the key
return -2;
return await GetApiKeyAsync(key, requestOptions, ct).ConfigureAwait(false);
}
catch (AlgoliaApiException e)
{
return e.HttpErrorCode;
if (e.HttpErrorCode is 404)
{
return null;
}

throw;
}
}, (status) =>
}, (response) =>
{
return operation switch
{
ApiKeyOperation.Add =>
// stop either when the key is created or when we don't receive 404
status is -2 or not 404 and not 0,
response is not null,
ApiKeyOperation.Delete =>
// stop when the key is not found
status == 404,
response is null,
_ => false
};
},
maxRetries, timeout, ct);
return addedKey;
}

/// <summary>
Expand All @@ -154,10 +154,10 @@ await RetryUntil(async () =>
/// <param name="timeout">The function to decide how long to wait between retries. Math.Min(retryCount * 200, 5000) by default. (optional)</param>
/// <param name="requestOptions">The requestOptions to send along with the query, they will be merged with the transporter requestOptions. (optional)</param>
/// <param name="ct">Cancellation token (optional)</param>
public GetApiKeyResponse WaitForApiKey(ApiKeyOperation operation, string key, ApiKey apiKey = default,
public GetApiKeyResponse WaitForApiKey(string key, ApiKeyOperation operation, ApiKey apiKey = default,
int maxRetries = DefaultMaxRetries, Func<int, int> timeout = null, RequestOptions requestOptions = null,
CancellationToken ct = default) =>
AsyncHelper.RunSync(() => WaitForApiKeyAsync(operation, key, apiKey, maxRetries, timeout, requestOptions, ct));
AsyncHelper.RunSync(() => WaitForApiKeyAsync(key, operation, apiKey, maxRetries, timeout, requestOptions, ct));


/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import kotlin.time.Duration.Companion.seconds
/**
* Wait for an API key to be added, updated or deleted based on a given `operation`.
*
* @param operation The `operation` that was done on a `key`.
* @param key The `key` that has been added, deleted or updated.
* @param operation The `operation` that was done on a `key`.
* @param apiKey Necessary to know if an `update` operation has been processed, compare fields of
* the response with it.
* @param maxRetries The maximum number of retries. 50 by default. (optional)
Expand All @@ -31,16 +31,16 @@ import kotlin.time.Duration.Companion.seconds
* the transporter requestOptions. (optional)
*/
public suspend fun SearchClient.waitForApiKey(
operation: ApiKeyOperation,
key: String,
operation: ApiKeyOperation,
apiKey: ApiKey? = null,
maxRetries: Int = 50,
timeout: Duration = Duration.INFINITE,
initialDelay: Duration = 200.milliseconds,
maxDelay: Duration = 5.seconds,
requestOptions: RequestOptions? = null,
) {
when (operation) {
): GetApiKeyResponse? {
return when (operation) {
ApiKeyOperation.Add -> waitKeyCreation(
key = key,
maxRetries = maxRetries,
Expand Down Expand Up @@ -226,25 +226,25 @@ public suspend fun SearchClient.waitKeyDelete(
initialDelay: Duration = 200.milliseconds,
maxDelay: Duration = 5.seconds,
requestOptions: RequestOptions? = null,
) {
retryUntil(
): GetApiKeyResponse? {
return retryUntil(
timeout = timeout,
maxRetries = maxRetries,
initialDelay = initialDelay,
maxDelay = maxDelay,
retry = {
try {
val response = getApiKey(key, requestOptions)
Result.success(response)
return@retryUntil getApiKey(key, requestOptions)
} catch (e: AlgoliaApiException) {
Result.failure(e)
if (e.httpErrorCode == 404) {
return@retryUntil null
}

throw e
}
},
until = { result ->
result.fold(
onSuccess = { false },
onFailure = { (it as AlgoliaApiException).httpErrorCode == 404 },
)
result == null
},
)
}
Expand Down
8 changes: 4 additions & 4 deletions clients/algoliasearch-client-php/lib/Support/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,13 @@ public static function retryForApiKeyUntil(

// In case of an addition, if there was no error, the $key has been added as it should be
if ('add' === $operation) {
return;
return $response;
}

// In case of an update, check if the key has been updated as it should be
if ('update' === $operation) {
if (self::isKeyUpdated($response, $apiKey)) {
return;
return $response;
}
}

Expand All @@ -166,9 +166,9 @@ public static function retryForApiKeyUntil(
// In case of a deletion, if there was an error, the $key has been deleted as it should be
if (
'delete' === $operation
&& 'Key does not exist' === $e->getMessage()
&& $e->getCode() === 404
) {
return;
return null;
}

// Else try again ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def request(call_type, method, path, body, opts = {})
)
if outcome == FAILURE
decoded_error = JSON.parse(response.error, :symbolize_names => true)
raise Algolia::AlgoliaHttpError.new(decoded_error[:status], decoded_error[:message])
raise Algolia::AlgoliaHttpError.new(response.status, decoded_error[:message])
end

return response unless outcome == RETRY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -128,7 +127,6 @@ private Map<String, Object> traverseParams(
String finalParamName = getFinalParamName(paramName);

testOutput.put("key", finalParamName);
testOutput.put("isKeyAllUpperCase", StringUtils.isAllUpperCase(finalParamName));
testOutput.put("useAnonymousKey", !finalParamName.matches("(.*)_[0-9]$") && depth != 0);
testOutput.put("parent", parent);
testOutput.put("isRoot", "".equals(parent));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ public void run(Map<String, CodegenModel> models, Map<String, CodegenOperation>
} else {
stepOut.put("match", step.expected.match);
}
} else if (step.expected.match == null) {
stepOut.put("match", Map.of());
stepOut.put("matchIsJSON", false);
stepOut.put("matchIsNull", true);
}
}
steps.add(stepOut);
Expand Down
2 changes: 1 addition & 1 deletion playground/swift/playground/playground/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Task {
taskIDs.append(saveObjRes.taskID)
}
for taskID in taskIDs {
try await client.waitForTask(with: taskID, in: indexName)
try await client.waitForTask(indexName: indexName, taskID: taskID)
}

let searchParams = SearchSearchParamsObject(query: "Jimmy")
Expand Down
2 changes: 2 additions & 0 deletions scripts/cts/runCts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { startTestServer } from './testServer';
import { assertChunkWrapperValid } from './testServer/chunkWrapper.js';
import { assertValidReplaceAllObjects } from './testServer/replaceAllObjects.js';
import { assertValidTimeouts } from './testServer/timeout.js';
import { assertValidWaitForApiKey } from './testServer/waitForApiKey.js';

async function runCtsOne(
language: Language,
Expand Down Expand Up @@ -152,5 +153,6 @@ export async function runCts(
assertValidTimeouts(languages.length);
assertChunkWrapperValid(languages.length - skip('dart') - skip('scala'));
assertValidReplaceAllObjects(languages.length - skip('dart') - skip('scala'));
assertValidWaitForApiKey(languages.length - skip('dart') - skip('scala'));
}
}
2 changes: 2 additions & 0 deletions scripts/cts/testServer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { gzipServer } from './gzip';
import { replaceAllObjectsServer } from './replaceAllObjects';
import { timeoutServer } from './timeout';
import { timeoutServerBis } from './timeoutBis';
import { waitForApiKeyServer } from './waitForApiKey';

export async function startTestServer(): Promise<() => Promise<void>> {
const servers = await Promise.all([
Expand All @@ -18,6 +19,7 @@ export async function startTestServer(): Promise<() => Promise<void>> {
timeoutServerBis(),
replaceAllObjectsServer(),
chunkWrapperServer(),
waitForApiKeyServer(),
]);

return async () => {
Expand Down
120 changes: 120 additions & 0 deletions scripts/cts/testServer/waitForApiKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import type { Server } from 'http';

import { expect } from 'chai';
import type { Express } from 'express';

import { setupServer } from '.';

const retryCount: Record<
string,
{
add: number;
update: number;
delete: number;
}
> = {};

export function assertValidWaitForApiKey(expectedCount: number): void {
expect(Object.keys(retryCount).length).to.be.equal(expectedCount);
for (const retry of Object.values(retryCount)) {
expect(retry).to.deep.equal({
add: 0,
update: 0,
delete: 0,
});
}
}

function addRoutes(app: Express): void {
app.get('/1/keys/:key', (req, res) => {
const lang = req.params.key.split('-').at(-1) as string;
if (!retryCount[lang]) {
retryCount[lang] = {
add: 0,
update: 0,
delete: 0,
};
}
const retry = retryCount[lang];
if (req.params.key === `api-key-add-operation-test-${lang}`) {
if (retry.add < 3) {
res.status(404).json({ message: `API key doesn't exist` });
} else if (retry.add === 3) {
res.status(200).json({
value: req.params.key,
description: 'my new api key',
acl: ['search', 'addObject'],
validity: 300,
maxQueriesPerIPPerHour: 100,
maxHitsPerQuery: 20,
createdAt: 1720094400,
});

retry.add = -1;
} else {
expect(retry.add).to.be.lessThan(3);
return;
}

retry.add += 1;
} else if (req.params.key === `api-key-update-operation-test-${lang}`) {
if (retry.update < 3) {
res.status(200).json({
value: req.params.key,
description: 'my new api key',
acl: ['search', 'addObject'],
validity: 300,
maxQueriesPerIPPerHour: 100,
maxHitsPerQuery: 20,
createdAt: 1720094400,
});
} else if (retry.update === 3) {
res.status(200).json({
value: req.params.key,
description: 'my updated api key',
acl: ['search', 'addObject', 'deleteObject'],
indexes: ['Movies', 'Books'],
referers: ['*google.com', '*algolia.com'],
validity: 305,
maxQueriesPerIPPerHour: 95,
maxHitsPerQuery: 20,
createdAt: 1720094400,
});

retry.update = -1;
} else {
expect(retry.update).to.be.lessThan(3);
return;
}

retry.update += 1;
} else if (req.params.key === `api-key-delete-operation-test-${lang}`) {
if (retry.delete < 3) {
res.status(200).json({
value: req.params.key,
description: 'my updated api key',
acl: ['search', 'addObject', 'deleteObject'],
validity: 305,
maxQueriesPerIPPerHour: 95,
maxHitsPerQuery: 20,
createdAt: 1720094400,
});
} else if (retry.delete === 3) {
res.status(404).json({ message: `API key doesn't exist` });

retry.delete = -1;
} else {
expect(retry.delete).to.be.lessThan(3);
return;
}

retry.delete += 1;
} else {
throw new Error(`Invalid API key ${req.params.key}`);
}
});
}

export function waitForApiKeyServer(): Promise<Server> {
return setupServer('waitForApiKey', 6681, addRoutes);
}
12 changes: 6 additions & 6 deletions specs/search/helpers/waitForApiKey.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ method:
summary: Wait for an API key operation
description: Waits for an API key to be added, updated, or deleted.
parameters:
- in: query
name: operation
description: Whether the API key was created, updated, or deleted.
required: true
schema:
$ref: '#/apiKeyOperation'
- in: query
name: key
description: API key to wait for.
required: true
schema:
type: string
- in: query
name: operation
description: Whether the API key was created, updated, or deleted.
required: true
schema:
$ref: '#/apiKeyOperation'
- in: query
name: apiKey
description: Used to compare fields of the `getApiKey` response on an `update` operation, to check if the `key` has been updated.
Expand Down
Loading
Loading