Skip to content

fix(account-client-copy): fixes unhandled promise issue #1154

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 19, 2020
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
@@ -1,23 +1,13 @@
import { accountCopyIndex } from '@algolia/client-account';
import { createRetryablePromise } from '@algolia/client-common';
import { Rule, Synonym } from '@algolia/client-search';

import { waitResponses } from '../../../../client-common/src/__tests__/helpers';
import { TestSuite } from '../../../../client-common/src/__tests__/TestSuite';

const testSuite = new TestSuite('account_copy_index');

test(testSuite.testName, async () => {
const source = testSuite.makeIndex();
const destination = testSuite
.makeSearchClient('ALGOLIA_APPLICATION_ID_2', 'ALGOLIA_ADMIN_KEY_2')
.initIndex(testSuite.makeIndexName());

await expect(accountCopyIndex(source, source)).rejects.toEqual({
name: 'IndicesInTheSameAppError',
message: 'Indices are in the same application. Use SearchClient.copyIndex instead.',
appId: source.appId,
});

describe(testSuite.testName, () => {
const rule: Rule = {
objectID: 'one',
condition: { anchoring: 'is', pattern: 'pattern' },
Expand All @@ -32,29 +22,101 @@ test(testSuite.testName, async () => {

const synomy: Synonym = { objectID: 'one', type: 'synonym', synonyms: ['one', 'two'] };

await waitResponses([
source.saveObject({ objectID: 'one' }),
source.setSettings({ searchableAttributes: ['objectID'] }),
source.saveRule(rule),
source.saveSynonym(synomy),
]);
const setUpSource = async (source: ReturnType<typeof testSuite.makeIndex>) => {
await waitResponses([
source.saveObject({ objectID: 'one' }),
source.setSettings({ searchableAttributes: ['objectID'] }),
source.saveRule(rule),
source.saveSynonym(synomy),
]);
};

const waitForIndexCreated = (destination: ReturnType<typeof testSuite.makeIndex>) =>
createRetryablePromise(async retry => {
const exists = await destination.exists();

return exists ? Promise.resolve() : retry();
});

it('copies indices between accounts', async () => {
const source = testSuite.makeIndex();
const destination = testSuite
.makeSearchClient('ALGOLIA_APPLICATION_ID_2', 'ALGOLIA_ADMIN_KEY_2')
.initIndex(testSuite.makeIndexName());

await setUpSource(source);

await expect(accountCopyIndex(source, source)).rejects.toEqual({
name: 'IndicesInTheSameAppError',
message: 'Indices are in the same application. Use SearchClient.copyIndex instead.',
appId: source.appId,
});

await expect(accountCopyIndex(source, destination).wait()).resolves.toBeUndefined();

await expect(destination.search('')).resolves.toMatchObject({
hits: [{ objectID: 'one' }],
});

await expect(accountCopyIndex(source, destination).wait()).resolves.toBeUndefined();
await expect(destination.getSettings()).resolves.toMatchObject({
searchableAttributes: ['objectID'],
});

await expect(destination.search('')).resolves.toMatchObject({
hits: [{ objectID: 'one' }],
await expect(destination.getRule('one')).resolves.toMatchObject(rule);

await expect(destination.getSynonym('one')).resolves.toEqual(synomy);

await expect(accountCopyIndex(source, destination)).rejects.toEqual({
name: 'DestinationIndiceAlreadyExistsError',
message: 'Destination indice already exists.',
});
});

await expect(destination.getSettings()).resolves.toMatchObject({
searchableAttributes: ['objectID'],
it('it does not need to wait', async () => {
const source = testSuite.makeIndex();
const destination = testSuite
.makeSearchClient('ALGOLIA_APPLICATION_ID_2', 'ALGOLIA_ADMIN_KEY_2')
.initIndex(testSuite.makeIndexName());

await setUpSource(source);

await expect(accountCopyIndex(source, destination)).resolves.toBeUndefined();

await waitForIndexCreated(destination);
await expect(destination.exists()).resolves.toEqual(true);
});

await expect(destination.getRule('one')).resolves.toEqual(rule);
it('it bubbles up errors', async () => {
const source = testSuite.makeIndex();

await setUpSource(source);

const indexName = testSuite.makeIndexName();

const addApiKeyResponse = await testSuite
.makeSearchClient('ALGOLIA_APPLICATION_ID_2', 'ALGOLIA_ADMIN_KEY_2')
.addApiKey(['settings', 'editSettings', 'search'], {
indexes: [indexName],
})
.wait();

const destination = testSuite
.algoliasearch(`${process.env.ALGOLIA_APPLICATION_ID_2}`, addApiKeyResponse.key)
.initIndex(indexName);

await expect(accountCopyIndex(source, destination).wait()).rejects.toMatchObject({
name: 'ApiError',
message: 'Not enough rights to update an object near line:1 column:64',
status: 400,
});

await expect(destination.getSynonym('one')).resolves.toEqual(synomy);
// At this point, we should have created the index. But it should
// be empty because we only have set settings on it.
await waitForIndexCreated(destination);

await expect(accountCopyIndex(source, destination)).rejects.toEqual({
name: 'DestinationIndiceAlreadyExistsError',
message: 'Destination indice already exists.',
await expect(destination.search('')).resolves.toMatchObject({
nbHits: 0,
hits: [],
});
});
});
20 changes: 16 additions & 4 deletions packages/client-account/src/methods/accountCopyIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,22 @@ export const accountCopyIndex = (
// eslint-disable-next-line functional/immutable-data
batch: objects => responses.push(saveObjects(destination)(objects, requestOptions)),
})
)
.then(() => Promise.resolve());
);

return createWaitablePromise(
/**
* The original promise will return an array of async responses, now
* we need to resolve that array of async responses using a
* `Promise.all`, and then resolve `void` for the end-user.
*/
promise.then(() => Promise.all(responses)).then(() => undefined),

return createWaitablePromise(promise, () =>
Promise.all(responses.map(response => response.wait()))
/**
* Next, if the end-user calls the `wait` method, we need to also call
* the `wait` method on each element of of async responses.
*/
(_response, waitRequestOptions) => {
return Promise.all(responses.map(response => response.wait(waitRequestOptions)));
}
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ test(testSuite.testName, async () => {
status: 404,
});

await expect(index.getRule('two')).resolves.toEqual(rule2);
await expect(index.getRule('two')).resolves.toMatchObject(rule2);
await expect(index.getRule('one')).rejects.toMatchObject({
name: 'ApiError',
message: 'ObjectID does not exist',
Expand Down