diff --git a/src/meilisearch.ts b/src/meilisearch.ts index b2e7d7527..142d5a0b9 100644 --- a/src/meilisearch.ts +++ b/src/meilisearch.ts @@ -34,6 +34,7 @@ import type { ResultsWrapper, WebhookCreatePayload, WebhookUpdatePayload, + UpdatableNetwork, } from "./types/index.js"; import { ErrorStatusCode } from "./types/index.js"; import { HttpRequests } from "./http-requests.js"; @@ -383,11 +384,8 @@ export class MeiliSearch { * * @experimental */ - async updateNetwork(network: Partial): Promise { - return await this.httpRequest.patch({ - path: "network", - body: network, - }); + async updateNetwork(options: UpdatableNetwork): Promise { + return await this.httpRequest.patch({ path: "network", body: options }); } /// diff --git a/src/types/index.ts b/src/types/index.ts index 6d744c635..f7fdf080d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,6 @@ export * from "./experimental-features.js"; -export * from "./task_and_batch.js"; +export * from "./network.js"; +export * from "./task-and-batch.js"; export * from "./token.js"; export * from "./types.js"; export * from "./webhooks.js"; diff --git a/src/types/network.ts b/src/types/network.ts new file mode 100644 index 000000000..778471dcb --- /dev/null +++ b/src/types/network.ts @@ -0,0 +1,17 @@ +import type { DeepPartial } from "./shared.js"; + +/** {@link https://www.meilisearch.com/docs/reference/api/network#the-remote-object} */ +export type Remote = { + url: string; + searchApiKey?: string | null; + writeApiKey?: string | null; +}; + +/** {@link https://www.meilisearch.com/docs/reference/api/network#the-network-object} */ +export type Network = { + self?: string | null; + remotes?: Record; + sharding?: boolean; +}; + +export type UpdatableNetwork = DeepPartial; diff --git a/src/types/shared.ts b/src/types/shared.ts index 42c558785..60673d3f4 100644 --- a/src/types/shared.ts +++ b/src/types/shared.ts @@ -1,5 +1,3 @@ -import type { RecordAny } from "./types.js"; - export type CursorResults = { results: T[]; limit: number; @@ -8,14 +6,10 @@ export type CursorResults = { total: number; }; -export type NonNullableDeepRecordValues = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [P in keyof T]: T[P] extends any[] - ? Array> - : T[P] extends RecordAny - ? NonNullableDeepRecordValues - : NonNullable; -}; +/** Deeply map every property of a record to itself making it partial. */ +export type DeepPartial = T extends object + ? { [TKey in keyof T]?: DeepPartial } + : T; // taken from https://stackoverflow.com/a/65642944 export type PascalToCamelCase = Uncapitalize; diff --git a/src/types/task_and_batch.ts b/src/types/task-and-batch.ts similarity index 90% rename from src/types/task_and_batch.ts rename to src/types/task-and-batch.ts index 0c30212f6..26892adc4 100644 --- a/src/types/task_and_batch.ts +++ b/src/types/task-and-batch.ts @@ -136,6 +136,18 @@ export type TaskDetails = Settings & upgradeTo: string; }>; +/** {@link https://www.meilisearch.com/docs/reference/api/tasks#network} */ +type Origin = { remoteName: string; taskUid: number }; + +/** {@link https://www.meilisearch.com/docs/reference/api/tasks#network} */ +type NetworkOrigin = { origin: Origin }; + +/** {@link https://www.meilisearch.com/docs/reference/api/tasks#network} */ +type RemoteTask = { taskUid?: number; error: MeiliSearchErrorResponse | null }; + +/** {@link https://www.meilisearch.com/docs/reference/api/tasks#network} */ +type NetworkRemoteTasks = { remoteTasks: Record }; + /** * {@link https://www.meilisearch.com/docs/reference/api/tasks#task-object} * @@ -150,6 +162,8 @@ export type Task = SafeOmit & { duration: string | null; startedAt: string | null; finishedAt: string | null; + /** {@link https://www.meilisearch.com/docs/reference/api/tasks#network} */ + network?: NetworkOrigin | NetworkRemoteTasks; }; /** diff --git a/src/types/types.ts b/src/types/types.ts index 3d57fb6d9..d47e61901 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -4,7 +4,7 @@ // Definitions: https://github.com/meilisearch/meilisearch-js // TypeScript Version: ^5.8.2 -import type { WaitOptions } from "./task_and_batch.js"; +import type { WaitOptions } from "./task-and-batch.js"; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type RecordAny = Record; @@ -310,26 +310,6 @@ export type FederatedMultiSearchParams = { queries: MultiSearchQueryWithFederation[]; }; -/** - * {@link https://www.meilisearch.com/docs/reference/api/network#the-remote-object} - * - * @see `meilisearch_types::features::Remote` at {@link https://github.com/meilisearch/meilisearch} - */ -export type Remote = { - url: string; - searchApiKey: string | null; -}; - -/** - * {@link https://www.meilisearch.com/docs/reference/api/network#the-network-object} - * - * @see `meilisearch_types::features::Network` at {@link https://github.com/meilisearch/meilisearch} - */ -export type Network = { - self: string | null; - remotes: Record; -}; - export type CategoriesDistribution = { [category: string]: number; }; @@ -842,12 +822,6 @@ export type Version = { ** ERROR HANDLER */ -export interface FetchError extends Error { - type: string; - errno: string; - code: string; -} - export type MeiliSearchErrorResponse = { message: string; // https://www.meilisearch.com/docs/reference/errors/error_codes diff --git a/tests/client.test.ts b/tests/client.test.ts index a426d5515..a1544dcfd 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -8,7 +8,13 @@ import { type MockInstance, beforeAll, } from "vitest"; -import type { Health, Version, Stats, IndexSwap } from "../src/index.js"; +import type { + Health, + Version, + Stats, + IndexSwap, + UpdatableNetwork, +} from "../src/index.js"; import { ErrorStatusCode, MeiliSearchRequestError } from "../src/index.js"; import pkg from "../package.json" with { type: "json" }; import { @@ -887,26 +893,22 @@ describe.each([{ permission: "Master" }])( test(`${permission} key: Update and get network settings`, async () => { const client = await getClient(permission); - const instances = { - [instanceName]: { - url: "http://instance-1:7700", - searchApiKey: "search-key-1", + const options: UpdatableNetwork = { + self: instanceName, + remotes: { + [instanceName]: { + url: "http://instance-1:7700", + searchApiKey: "search-key-1", + writeApiKey: "write-key-1", + }, }, + sharding: true, }; - await client.updateNetwork({ self: instanceName, remotes: instances }); + await client.updateNetwork(options); const response = await client.getNetwork(); - expect(response).toHaveProperty("self", instanceName); - expect(response).toHaveProperty("remotes"); - expect(response.remotes).toHaveProperty("instance_1"); - expect(response.remotes["instance_1"]).toHaveProperty( - "url", - instances[instanceName].url, - ); - expect(response.remotes["instance_1"]).toHaveProperty( - "searchApiKey", - instances[instanceName].searchApiKey, - ); + + assert.deepEqual(response, options); }); }, ); diff --git a/tests/tasks-and-batches.test.ts b/tests/tasks-and-batches.test.ts index 0fb303272..b80a61c86 100644 --- a/tests/tasks-and-batches.test.ts +++ b/tests/tasks-and-batches.test.ts @@ -1,12 +1,15 @@ import { randomUUID } from "node:crypto"; import { beforeAll, describe, test, vi } from "vitest"; import type { TasksOrBatchesQuery } from "../src/types/index.js"; -import { getClient, objectEntries } from "./utils/meilisearch-test-utils.js"; import { + getClient, + objectEntries, assert, - possibleTaskTypes, +} from "./utils/meilisearch-test-utils.js"; +import { possibleTaskStatuses, -} from "./utils/tasks-and-batches.js"; + possibleTaskTypes, +} from "./utils/assertions/tasks-and-batches.js"; const INDEX_UID = randomUUID(); const ms = await getClient("Master"); @@ -188,11 +191,11 @@ describe.for(objectEntries(testValuesRecord))("%s", ([key, testValues]) => { test.for(testValues)( `${ms.tasks.getTasks.name} method%s`, async ([, value]) => { - const { results, ...r } = await ms.tasks.getTasks({ [key]: value }); + const tasksResults = await ms.tasks.getTasks({ [key]: value }); - assert.isResult(r); + assert.isTasksOrBatchesResults(tasksResults); - for (const task of results) { + for (const task of tasksResults.results) { assert.isTask(task); } }, @@ -201,11 +204,11 @@ describe.for(objectEntries(testValuesRecord))("%s", ([key, testValues]) => { test.for(testValues)( `${ms.batches.getBatches.name} method%s`, async ([, value]) => { - const { results, ...r } = await ms.batches.getBatches({ [key]: value }); + const batchesResults = await ms.batches.getBatches({ [key]: value }); - assert.isResult(r); + assert.isTasksOrBatchesResults(batchesResults); - for (const batch of results) { + for (const batch of batchesResults.results) { assert.isBatch(batch); } }, diff --git a/tests/utils/assert.ts b/tests/utils/assert.ts new file mode 100644 index 000000000..5ea99960b --- /dev/null +++ b/tests/utils/assert.ts @@ -0,0 +1,18 @@ +import { assert } from "vitest"; +import { errorAssertions } from "./assertions/error.js"; +import { promiseAssertions } from "./assertions/promise.js"; +import { tasksAndBatchesAssertions } from "./assertions/tasks-and-batches.js"; + +const source = { + ...errorAssertions, + ...promiseAssertions, + ...tasksAndBatchesAssertions, +}; + +const customAssert: typeof assert & typeof source = Object.assign( + assert, + source, +); + +// needs to be named assert to satisfy Vitest ESLint plugin in tests +export { customAssert as assert }; diff --git a/tests/utils/assertions/error.ts b/tests/utils/assertions/error.ts new file mode 100644 index 000000000..a17047494 --- /dev/null +++ b/tests/utils/assertions/error.ts @@ -0,0 +1,12 @@ +import { assert } from "vitest"; +import type { MeiliSearchErrorResponse } from "../../../src/index.js"; + +export const errorAssertions = { + isErrorResponse(error: MeiliSearchErrorResponse) { + assert.lengthOf(Object.keys(error), 4); + const { message, code, type, link } = error; + for (const val of Object.values({ message, code, type, link })) { + assert.typeOf(val, "string"); + } + }, +}; diff --git a/tests/utils/assertions/promise.ts b/tests/utils/assertions/promise.ts new file mode 100644 index 000000000..e60fc7e22 --- /dev/null +++ b/tests/utils/assertions/promise.ts @@ -0,0 +1,43 @@ +import { assert } from "vitest"; + +const NOT_RESOLVED = Symbol(""); +const RESOLVED = Symbol(""); + +export const promiseAssertions = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async rejects( + promise: Promise, + errorConstructor: T, + errMsgMatcher?: RegExp | string, + ): Promise> { + let resolvedValue; + + try { + resolvedValue = await promise; + } catch (error) { + assert.instanceOf(error, errorConstructor); + + if (errMsgMatcher !== undefined) { + const { message } = error as Error; + if (typeof errMsgMatcher === "string") { + assert.strictEqual(message, errMsgMatcher); + } else { + assert.match(message, errMsgMatcher); + } + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return error as InstanceType; + } + + assert.fail(resolvedValue, NOT_RESOLVED, "expected value to not resolve"); + }, + + async resolves(promise: Promise): Promise { + try { + await promise; + } catch (error) { + assert.fail(error, RESOLVED, "expected value to not reject"); + } + }, +}; diff --git a/tests/utils/assertions/tasks-and-batches.ts b/tests/utils/assertions/tasks-and-batches.ts new file mode 100644 index 000000000..bcf213716 --- /dev/null +++ b/tests/utils/assertions/tasks-and-batches.ts @@ -0,0 +1,229 @@ +import type { + TasksResults, + Batch, + TaskType, + TaskStatus, + EnqueuedTask, + BatchesResults, + Task, +} from "../../../src/types/index.js"; +import { assert } from "vitest"; +import { objectKeys } from "../object.js"; +import { errorAssertions } from "./error.js"; + +export const possibleTaskStatuses = objectKeys({ + enqueued: null, + processing: null, + succeeded: null, + failed: null, + canceled: null, +}); + +export const possibleTaskTypes = objectKeys({ + documentAdditionOrUpdate: null, + documentEdition: null, + documentDeletion: null, + settingsUpdate: null, + indexCreation: null, + indexDeletion: null, + indexUpdate: null, + indexSwap: null, + taskCancelation: null, + taskDeletion: null, + dumpCreation: null, + snapshotCreation: null, + upgradeDatabase: null, +}); + +export const tasksAndBatchesAssertions = { + isEnqueuedTask(enqueuedTask: EnqueuedTask) { + assert.lengthOf(Object.keys(enqueuedTask), 5); + + const { taskUid, indexUid, status, type, enqueuedAt } = enqueuedTask; + + assert.typeOf(taskUid, "number"); + assert( + indexUid === null || typeof indexUid === "string", + `expected ${indexUid} to be null or string`, + ); + assert.oneOf(status, possibleTaskStatuses); + assert.oneOf(type, possibleTaskTypes); + assert.typeOf(enqueuedAt, "string"); + }, + + isBatch(batch: Batch) { + assert.lengthOf(Object.keys(batch), 8); + + const { uid, progress, details, stats, duration, startedAt, finishedAt } = + batch; + + assert.typeOf(uid, "number"); + assert( + typeof progress === "object", + "expected progress to be of type object or null", + ); + + if (progress !== null) { + assert.lengthOf(Object.keys(progress), 2); + const { steps, percentage } = progress; + + for (const step of steps) { + assert.lengthOf(Object.keys(step), 3); + + const { currentStep, finished, total } = step; + + assert.typeOf(currentStep, "string"); + assert.typeOf(finished, "number"); + assert.typeOf(total, "number"); + } + + assert.typeOf(percentage, "number"); + } + + assert.typeOf(details, "object"); + + const { length } = Object.keys(stats); + + assert.isAtLeast(length, 4); + assert.isAtMost(length, 7); + + const { + totalNbTasks, + status, + types, + indexUids, + progressTrace, + writeChannelCongestion, + internalDatabaseSizes, + } = stats; + + assert.typeOf(totalNbTasks, "number"); + + for (const [key, val] of Object.entries(status)) { + assert.oneOf(key, possibleTaskStatuses); + assert.typeOf(val, "number"); + } + + for (const [key, val] of Object.entries(types)) { + assert.oneOf(key, possibleTaskTypes); + assert.typeOf(val, "number"); + } + + for (const val of Object.values(indexUids)) { + assert.typeOf(val, "number"); + } + + assert( + progressTrace === undefined || + (progressTrace !== null && typeof progressTrace === "object"), + "expected progressTrace to be undefined or an object", + ); + + assert( + writeChannelCongestion === undefined || + (writeChannelCongestion !== null && + typeof writeChannelCongestion === "object"), + "expected writeChannelCongestion to be undefined or an object", + ); + + assert( + internalDatabaseSizes === undefined || + (internalDatabaseSizes !== null && + typeof internalDatabaseSizes === "object"), + "expected internalDatabaseSizes to be undefined or an object", + ); + + assert( + duration === null || typeof duration === "string", + "expected duration to be null or string", + ); + + assert.typeOf(startedAt, "string"); + + assert( + finishedAt === null || typeof finishedAt === "string", + "expected finishedAt to be null or string", + ); + }, + + isTasksOrBatchesResults(value: TasksResults | BatchesResults) { + assert.lengthOf(Object.keys(value), 5); + + const { results, total, limit, from, next } = value; + + // it's up to individual tests to assert the exact type of each element + assert.isArray(results); + + assert.typeOf(total, "number"); + assert.typeOf(limit, "number"); + + if (from !== null) { + assert.typeOf(from, "number"); + } + + if (next !== null) { + assert.typeOf(next, "number"); + } + }, + + isTask(task: Task) { + const { length } = Object.keys(task); + + assert.isAtLeast(length, 11); + assert.isAtMost(length, 13); + + const { + indexUid, + status, + type, + enqueuedAt, + uid, + batchUid, + canceledBy, + details, + error, + duration, + startedAt, + finishedAt, + network, + } = task; + + assert(indexUid === null || typeof indexUid === "string"); + + assert.oneOf(status, possibleTaskStatuses); + + assert.oneOf(type, possibleTaskTypes); + + assert.typeOf(enqueuedAt, "string"); + assert.typeOf(uid, "number"); + assert(batchUid === null || typeof batchUid === "number"); + assert(canceledBy === null || typeof canceledBy === "number"); + + // it's up to individual tests to assert the exact shape of this property + assert( + details === undefined || + (details !== null && typeof details === "object"), + ); + + assert(typeof error === "object"); + if (error !== null) { + errorAssertions.isErrorResponse(error); + } + + assert(duration === null || typeof duration === "string"); + assert(startedAt === null || typeof startedAt === "string"); + assert(finishedAt === null || typeof finishedAt === "string"); + + // it's up to individual tests to assert the exact shape of this property + assert( + network === undefined || + (network !== null && typeof network === "object"), + ); + }, + + isTaskSuccessful(task: Task) { + this.isTask(task); + assert.isNull(task.error); + assert.strictEqual(task.status, "succeeded"); + }, +}; diff --git a/tests/utils/meilisearch-test-utils.ts b/tests/utils/meilisearch-test-utils.ts index 234e502d5..1a2121580 100644 --- a/tests/utils/meilisearch-test-utils.ts +++ b/tests/utils/meilisearch-test-utils.ts @@ -1,12 +1,4 @@ -import { assert as vitestAssert } from "vitest"; -import { MeiliSearch, Index } from "../../src/index.js"; -import type { - Config, - TaskType, - MeiliSearchErrorResponse, - TaskStatus, - Task, -} from "../../src/index.js"; +import { type Config, MeiliSearch, Index } from "../../src/index.js"; // testing const MASTER_KEY = "masterKey"; @@ -103,136 +95,6 @@ function decode64(buff: string) { return Buffer.from(buff, "base64").toString(); } -const NOT_RESOLVED = Symbol(""); -const RESOLVED = Symbol(""); - -const source = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async rejects( - promise: Promise, - errorConstructor: T, - errMsgMatcher?: RegExp | string, - ): Promise> { - let resolvedValue; - - try { - resolvedValue = await promise; - } catch (error) { - vitestAssert.instanceOf(error, errorConstructor); - - if (errMsgMatcher !== undefined) { - const { message } = error as Error; - if (typeof errMsgMatcher === "string") { - vitestAssert.strictEqual(message, errMsgMatcher); - } else { - vitestAssert.match(message, errMsgMatcher); - } - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return error as InstanceType; - } - - vitestAssert.fail( - resolvedValue, - NOT_RESOLVED, - "expected value to not resolve", - ); - }, - async resolves(promise: Promise): Promise { - try { - await promise; - } catch (error) { - vitestAssert.fail(error, RESOLVED, "expected value to not reject"); - } - }, - isErrorResponse(error: MeiliSearchErrorResponse) { - vitestAssert.lengthOf(Object.keys(error), 4); - const { message, code, type, link } = error; - for (const val of Object.values({ message, code, type, link })) { - vitestAssert.typeOf(val, "string"); - } - }, - isTask(task: Task) { - const { length } = Object.keys(task); - vitestAssert(length >= 11 && length <= 12); - const { - indexUid, - status, - type, - enqueuedAt, - uid, - batchUid, - canceledBy, - details, - error, - duration, - startedAt, - finishedAt, - } = task; - - vitestAssert(indexUid === null || typeof indexUid === "string"); - - vitestAssert.oneOf( - status, - objectKeys({ - enqueued: null, - processing: null, - succeeded: null, - failed: null, - canceled: null, - }), - ); - - vitestAssert.oneOf( - type, - objectKeys({ - documentAdditionOrUpdate: null, - documentEdition: null, - documentDeletion: null, - settingsUpdate: null, - indexCreation: null, - indexDeletion: null, - indexUpdate: null, - indexSwap: null, - taskCancelation: null, - taskDeletion: null, - dumpCreation: null, - snapshotCreation: null, - upgradeDatabase: null, - }), - ); - - vitestAssert.typeOf(enqueuedAt, "string"); - vitestAssert.typeOf(uid, "number"); - vitestAssert(batchUid === null || typeof batchUid === "number"); - vitestAssert(canceledBy === null || typeof canceledBy === "number"); - - vitestAssert( - details === undefined || - (details !== null && typeof details === "object"), - ); - - vitestAssert(typeof error === "object"); - if (error !== null) { - this.isErrorResponse(error); - } - - vitestAssert(duration === null || typeof duration === "string"); - vitestAssert(startedAt === null || typeof startedAt === "string"); - vitestAssert(finishedAt === null || typeof finishedAt === "string"); - }, - isTaskSuccessful(task: Task) { - this.isTask(task); - vitestAssert.isNull(task.error); - vitestAssert.strictEqual(task.status, "succeeded"); - }, -}; -export const assert: typeof vitestAssert & typeof source = Object.assign( - vitestAssert, - source, -); - const datasetWithNests = [ { id: 1, @@ -344,18 +206,9 @@ export type Book = { author: string; }; -function objectKeys(o: { [TKey in T]: null }): T[] { - return Object.keys(o) as T[]; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const objectEntries = Object.entries as >( - o: T, -) => [key: keyof T, val: T[keyof T]][]; - +export * from "./assert.js"; +export * from "./object.js"; export { - objectEntries, - objectKeys, clearAllIndexes, config, masterClient, diff --git a/tests/utils/object.ts b/tests/utils/object.ts new file mode 100644 index 000000000..e77f5a0cd --- /dev/null +++ b/tests/utils/object.ts @@ -0,0 +1,8 @@ +export function objectKeys(o: { [TKey in T]: null }): T[] { + return Object.keys(o) as T[]; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const objectEntries = Object.entries as >( + o: T, +) => [key: keyof T, val: T[keyof T]][]; diff --git a/tests/utils/tasks-and-batches.ts b/tests/utils/tasks-and-batches.ts deleted file mode 100644 index 79a56ae7c..000000000 --- a/tests/utils/tasks-and-batches.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { assert as extAssert, objectKeys } from "./meilisearch-test-utils.js"; -import type { - TasksResults, - Batch, - TaskType, - TaskStatus, - EnqueuedTask, -} from "../../src/types/index.js"; -import type { SafeOmit } from "../../src/types/shared.js"; - -export const possibleTaskStatuses = objectKeys({ - enqueued: null, - processing: null, - succeeded: null, - failed: null, - canceled: null, -}); - -export const possibleTaskTypes = objectKeys({ - documentAdditionOrUpdate: null, - documentEdition: null, - documentDeletion: null, - settingsUpdate: null, - indexCreation: null, - indexDeletion: null, - indexUpdate: null, - indexSwap: null, - taskCancelation: null, - taskDeletion: null, - dumpCreation: null, - snapshotCreation: null, - upgradeDatabase: null, -}); - -const customAssertions = { - isEnqueuedTask(enqueuedTask: EnqueuedTask) { - extAssert.lengthOf(Object.keys(enqueuedTask), 5); - - const { taskUid, indexUid, status, type, enqueuedAt } = enqueuedTask; - - extAssert.typeOf(taskUid, "number"); - extAssert( - indexUid === null || typeof indexUid === "string", - `expected ${indexUid} to be null or string`, - ); - extAssert.oneOf(status, possibleTaskStatuses); - extAssert.oneOf(type, possibleTaskTypes); - extAssert.typeOf(enqueuedAt, "string"); - }, - - isBatch(batch: Batch) { - extAssert.lengthOf(Object.keys(batch), 8); - - const { uid, progress, details, stats, duration, startedAt, finishedAt } = - batch; - - extAssert.typeOf(uid, "number"); - extAssert( - typeof progress === "object", - "expected progress to be of type object or null", - ); - - if (progress !== null) { - extAssert.lengthOf(Object.keys(progress), 2); - const { steps, percentage } = progress; - - for (const step of steps) { - extAssert.lengthOf(Object.keys(step), 3); - - const { currentStep, finished, total } = step; - - extAssert.typeOf(currentStep, "string"); - extAssert.typeOf(finished, "number"); - extAssert.typeOf(total, "number"); - } - - extAssert.typeOf(percentage, "number"); - } - - extAssert.typeOf(details, "object"); - - const { length } = Object.keys(stats); - - extAssert.isAtLeast(length, 4); - extAssert.isAtMost(length, 7); - - const { - totalNbTasks, - status, - types, - indexUids, - progressTrace, - writeChannelCongestion, - internalDatabaseSizes, - } = stats; - - extAssert.typeOf(totalNbTasks, "number"); - - for (const [key, val] of Object.entries(status)) { - extAssert.oneOf(key, possibleTaskStatuses); - extAssert.typeOf(val, "number"); - } - - for (const [key, val] of Object.entries(types)) { - extAssert.oneOf(key, possibleTaskTypes); - extAssert.typeOf(val, "number"); - } - - for (const val of Object.values(indexUids)) { - extAssert.typeOf(val, "number"); - } - - extAssert( - progressTrace === undefined || - (progressTrace !== null && typeof progressTrace === "object"), - "expected progressTrace to be undefined or an object", - ); - - extAssert( - writeChannelCongestion === undefined || - (writeChannelCongestion !== null && - typeof writeChannelCongestion === "object"), - "expected writeChannelCongestion to be undefined or an object", - ); - - extAssert( - internalDatabaseSizes === undefined || - (internalDatabaseSizes !== null && - typeof internalDatabaseSizes === "object"), - "expected internalDatabaseSizes to be undefined or an object", - ); - - extAssert( - duration === null || typeof duration === "string", - "expected duration to be null or string", - ); - - extAssert.typeOf(startedAt, "string"); - - extAssert( - finishedAt === null || typeof finishedAt === "string", - "expected finishedAt to be null or string", - ); - }, - - isResult(value: SafeOmit) { - extAssert.lengthOf(Object.keys(value), 4); - extAssert.typeOf(value.total, "number"); - extAssert.typeOf(value.limit, "number"); - extAssert( - value.from === null || typeof value.from === "number", - "expected from to be null or number", - ); - extAssert( - value.next === null || typeof value.next === "number", - "expected next to be null or number", - ); - }, -}; - -export const assert: typeof extAssert & typeof customAssertions = Object.assign( - extAssert, - customAssertions, -);