From 0ab941ed89d0c8ffddb22010c69862878b0f3870 Mon Sep 17 00:00:00 2001 From: Duda Nogueira Date: Mon, 3 Aug 2026 18:27:36 -0300 Subject: [PATCH 1/3] feat(backup): support incremental base backup ID in the v2 creator Adds .withIncrementalBaseBackupId() to BackupCreator so the incremental_base_backup_id field is sent on backup creation, plus validation rejecting an empty base ID or one equal to the backup being created. --- src/backup/backupCreator.ts | 18 ++++++++++++++++++ src/backup/validation.ts | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/backup/backupCreator.ts b/src/backup/backupCreator.ts index 5690f73e..0d60c57c 100644 --- a/src/backup/backupCreator.ts +++ b/src/backup/backupCreator.ts @@ -14,6 +14,7 @@ import { validateBackupId, validateExcludeClassNames, validateIncludeClassNames, + validateIncrementalBaseBackupId, } from './validation.js'; const WAIT_INTERVAL = 1000; @@ -26,6 +27,7 @@ export default class BackupCreator extends CommandBase { private statusGetter: BackupCreateStatusGetter; private waitForCompletion!: boolean; private config?: BackupConfig; + private incrementalBaseBackupId?: string; constructor(client: Connection, statusGetter: BackupCreateStatusGetter) { super(client); @@ -70,12 +72,27 @@ export default class BackupCreator extends CommandBase { return this; } + /** + * The ID of an existing backup to use as the base for a file-based incremental backup. + * Only the files that changed since the base backup are included in the new backup. + * + * `backupId` is a plain backup ID string: either a literal, e.g. `'my-base-backup'`, or the + * `id` returned by a previous backup creation. + * + * Requires Weaviate v1.37.0 or higher. + */ + withIncrementalBaseBackupId(backupId: string) { + this.incrementalBaseBackupId = backupId; + return this; + } + validate = (): void => { this.addErrors([ ...validateIncludeClassNames(this.includeClassNames), ...validateExcludeClassNames(this.excludeClassNames), ...validateBackend(this.backend), ...validateBackupId(this.backupId), + ...validateIncrementalBaseBackupId(this.incrementalBaseBackupId, this.backupId), ]); }; @@ -90,6 +107,7 @@ export default class BackupCreator extends CommandBase { config: this.config, include: this.includeClassNames, exclude: this.excludeClassNames, + incremental_base_backup_id: this.incrementalBaseBackupId, } as BackupCreateRequest; if (this.waitForCompletion) { diff --git a/src/backup/validation.ts b/src/backup/validation.ts index 778ecb87..4a9ceffb 100644 --- a/src/backup/validation.ts +++ b/src/backup/validation.ts @@ -45,3 +45,18 @@ export function validateBackupId(backupId?: string) { } return []; } + +export function validateIncrementalBaseBackupId(incrementalBaseBackupId?: string, backupId?: string) { + if (incrementalBaseBackupId === undefined || incrementalBaseBackupId === null) { + return []; + } + if (!isValidStringProperty(incrementalBaseBackupId)) { + return [ + 'string incrementalBaseBackupId must be a non-empty string - set with .withIncrementalBaseBackupId(backupId)', + ]; + } + if (incrementalBaseBackupId === backupId) { + return ['incrementalBaseBackupId must be different from the ID of the backup being created']; + } + return []; +} From 1ff159383390898ac47a59102b79f04ffddd4b8d Mon Sep 17 00:00:00 2001 From: Duda Nogueira Date: Mon, 3 Aug 2026 18:28:37 -0300 Subject: [PATCH 2/3] feat(backup): expose incremental backups in the collections client client.backup.create() and collection.backup.create() now accept incrementalBaseBackupId. The request is gated client-side on Weaviate >=1.37.0 via DbVersionSupport.supportsIncrementalBackups() and throws WeaviateUnsupportedFeatureError on older servers. The base backup ID is also surfaced on getCreateStatus() and list(), which Weaviate only returns to root users. --- src/collections/backup/client.ts | 45 +++++++++++++++++++++++----- src/collections/backup/collection.ts | 33 ++++++++++++++++---- src/collections/backup/index.ts | 10 +++++-- src/collections/backup/types.ts | 16 ++++++++++ src/collections/collection/index.ts | 2 +- src/index.ts | 2 +- src/openapi/types.ts | 1 + src/utils/dbVersion.ts | 7 +++++ 8 files changed, 100 insertions(+), 16 deletions(-) diff --git a/src/collections/backup/client.ts b/src/collections/backup/client.ts index 4a655f10..edbb87b0 100644 --- a/src/collections/backup/client.ts +++ b/src/collections/backup/client.ts @@ -5,7 +5,11 @@ import { BackupRestoreStatusGetter, BackupRestorer, } from '../../backup/index.js'; -import { validateBackend, validateBackupId } from '../../backup/validation.js'; +import { + validateBackend, + validateBackupId, + validateIncrementalBaseBackupId, +} from '../../backup/validation.js'; import Connection from '../../connection/index.js'; import { WeaviateBackupCanceled, @@ -14,24 +18,27 @@ import { WeaviateInvalidInputError, WeaviateUnexpectedResponseError, WeaviateUnexpectedStatusCodeError, + WeaviateUnsupportedFeatureError, } from '../../errors.js'; import { BackupCreateResponse, BackupCreateStatusResponse, + BackupListResponse, BackupRestoreResponse, } from '../../openapi/types.js'; +import { DbVersionSupport } from '../../utils/dbVersion.js'; import { BackupArgs, BackupCancelArgs, - BackupConfigCreate, BackupConfigRestore, + BackupCreateArgs, BackupReturn, BackupStatusArgs, BackupStatusReturn, ListBackupOptions, } from './types.js'; -export const backup = (connection: Connection): Backup => { +export const backup = (connection: Connection, dbVersionSupport: DbVersionSupport): Backup => { const parseStatus = (res: BackupCreateStatusResponse | BackupRestoreResponse): BackupStatusReturn => { if (res.id === undefined) { throw new WeaviateUnexpectedResponseError('Backup ID is undefined in response'); @@ -47,6 +54,9 @@ export const backup = (connection: Connection): Backup => { error: res.error, path: res.path, status: res.status, + // Only returned by Weaviate >=1.37, for incremental backups, and only to root users + incrementalBaseBackupId: + 'incremental_base_backup_id' in res ? res.incremental_base_backup_id || undefined : undefined, }; }; const parseResponse = (res: BackupCreateResponse | BackupRestoreResponse): BackupReturn => { @@ -109,10 +119,22 @@ export const backup = (connection: Connection): Backup => { return true; }, - create: async (args: BackupArgs): Promise => { + create: async (args: BackupCreateArgs): Promise => { let builder = new BackupCreator(connection, new BackupCreateStatusGetter(connection)) .withBackupId(args.backupId) .withBackend(args.backend); + if (args.incrementalBaseBackupId !== undefined) { + const baseBackupId = args.incrementalBaseBackupId.toLowerCase(); + const errors = validateIncrementalBaseBackupId(baseBackupId, args.backupId.toLowerCase()); + if (errors.length > 0) { + throw new WeaviateInvalidInputError(errors.join(', ')); + } + const check = await dbVersionSupport.supportsIncrementalBackups(); + if (!check.supports) { + throw new WeaviateUnsupportedFeatureError(check.message); + } + builder = builder.withIncrementalBaseBackupId(baseBackupId); + } if (args.includeCollections) { builder = builder.withIncludeClassNames(...args.includeCollections); } @@ -213,7 +235,12 @@ export const backup = (connection: Connection): Backup => { if (opts?.startedAtAsc) { url += '?order=asc'; } - return connection.get(url); + return connection.get(url).then((res) => + res.map(({ incremental_base_backup_id: baseBackupId, ...rest }) => ({ + ...rest, + incrementalBaseBackupId: baseBackupId || undefined, + })) + ) as Promise; }, }; }; @@ -231,13 +258,17 @@ export interface Backup { /** * Create a backup of the database. * - * @param {BackupArgs} args The arguments for the request. + * Pass `incrementalBaseBackupId` to create a file-based incremental backup, which only + * contains the files that changed since the given base backup. Requires Weaviate `v1.37.0` or higher. + * + * @param {BackupCreateArgs} args The arguments for the request. * @returns {Promise} The response from Weaviate. * @throws {WeaviateInvalidInputError} If the input is invalid. + * @throws {WeaviateUnsupportedFeatureError} If `incrementalBaseBackupId` is used with Weaviate <1.37.0. * @throws {WeaviateBackupFailed} If the backup creation fails. * @throws {WeaviateBackupCanceled} If the backup creation is canceled. */ - create(args: BackupArgs): Promise; + create(args: BackupCreateArgs): Promise; /** * Get the status of a backup creation. * diff --git a/src/collections/backup/collection.ts b/src/collections/backup/collection.ts index 377d8009..04c1a528 100644 --- a/src/collections/backup/collection.ts +++ b/src/collections/backup/collection.ts @@ -1,5 +1,6 @@ import { Backend } from '../../backup/index.js'; import Connection from '../../connection/index.js'; +import { DbVersionSupport } from '../../utils/dbVersion.js'; import { backup } from './client.js'; import { BackupReturn, BackupStatusArgs, BackupStatusReturn } from './types.js'; @@ -13,10 +14,28 @@ export type BackupCollectionArgs = { waitForCompletion?: boolean; }; -export const backupCollection = (connection: Connection, name: string) => { - const handler = backup(connection); +/** The arguments required to create a backup of a collection. */ +export type BackupCollectionCreateArgs = BackupCollectionArgs & { + /** + * The ID of an existing backup to use as the base for a file-based incremental backup. + * If set, only the files that have changed since the base backup are included in the new backup. + * + * This is a plain backup ID string: either a literal, e.g. `'my-base-backup'`, or the `id` + * returned by a previous backup creation. + * + * Requires Weaviate `v1.37.0` or higher. + */ + incrementalBaseBackupId?: string; +}; + +export const backupCollection = ( + connection: Connection, + name: string, + dbVersionSupport: DbVersionSupport +) => { + const handler = backup(connection, dbVersionSupport); return { - create: (args: BackupCollectionArgs) => + create: (args: BackupCollectionCreateArgs) => handler.create({ ...args, includeCollections: [name], @@ -35,13 +54,17 @@ export interface BackupCollection { /** * Create a backup of this collection. * - * @param {BackupArgs} args The arguments for the request. + * Pass `incrementalBaseBackupId` to create a file-based incremental backup, which only + * contains the files that changed since the given base backup. Requires Weaviate `v1.37.0` or higher. + * + * @param {BackupCollectionCreateArgs} args The arguments for the request. * @returns {Promise} The response from Weaviate. * @throws {WeaviateInvalidInputError} If the input is invalid. + * @throws {WeaviateUnsupportedFeatureError} If `incrementalBaseBackupId` is used with Weaviate <1.37.0. * @throws {WeaviateBackupFailed} If the backup creation fails. * @throws {WeaviateBackupCanceled} If the backup creation is canceled. */ - create(args: BackupCollectionArgs): Promise; + create(args: BackupCollectionCreateArgs): Promise; /** * Get the status of a backup. * diff --git a/src/collections/backup/index.ts b/src/collections/backup/index.ts index e4c6f1b1..baea10f6 100644 --- a/src/collections/backup/index.ts +++ b/src/collections/backup/index.ts @@ -1,3 +1,9 @@ export type { Backup } from './client.js'; -export type { BackupCollection, BackupCollectionArgs } from './collection.js'; -export type { BackupArgs, BackupConfigCreate, BackupConfigRestore, BackupStatusArgs } from './types.js'; +export type { BackupCollection, BackupCollectionArgs, BackupCollectionCreateArgs } from './collection.js'; +export type { + BackupArgs, + BackupConfigCreate, + BackupConfigRestore, + BackupCreateArgs, + BackupStatusArgs, +} from './types.js'; diff --git a/src/collections/backup/types.ts b/src/collections/backup/types.ts index c76e5a25..546694a6 100644 --- a/src/collections/backup/types.ts +++ b/src/collections/backup/types.ts @@ -16,6 +16,8 @@ export type BackupStatusReturn = { status: BackupStatus; /** Size of the backup in Gibs */ size?: number; + /** The ID of the base backup this incremental backup was built on; undefined if the backup is not incremental. */ + incrementalBaseBackupId?: string; }; /** The return type of a backup creation or restoration operation */ @@ -64,6 +66,20 @@ export type BackupArgs = { config?: C; }; +/** The arguments required to create a backup. */ +export type BackupCreateArgs = BackupArgs & { + /** + * The ID of an existing backup to use as the base for a file-based incremental backup. + * If set, only the files that have changed since the base backup are included in the new backup. + * + * This is a plain backup ID string: either a literal, e.g. `'my-base-backup'`, or the `id` + * returned by a previous backup creation. + * + * Requires Weaviate `v1.37.0` or higher. + */ + incrementalBaseBackupId?: string; +}; + /** The arguments required to get the status of a backup. */ export type BackupStatusArgs = { /** The ID of the backup. */ diff --git a/src/collections/collection/index.ts b/src/collections/collection/index.ts index c4164352..c0676216 100644 --- a/src/collections/collection/index.ts +++ b/src/collections/collection/index.ts @@ -138,7 +138,7 @@ const collection = ( ); return { aggregate: aggregateCollection, - backup: backupCollection(connection, capitalizedName), + backup: backupCollection(connection, capitalizedName, dbVersionSupport), config: config(connection, capitalizedName, dbVersionSupport, tenant), data: data(connection, capitalizedName, dbVersionSupport, consistencyLevel, tenant), filter: filter(), diff --git a/src/index.ts b/src/index.ts index b3fa9e7d..b9cf3926 100644 --- a/src/index.ts +++ b/src/index.ts @@ -233,7 +233,7 @@ async function client(params: ClientParams): Promise { const ifc: WeaviateClient = { alias: alias(connection), - backup: backup(connection), + backup: backup(connection, dbVersionSupport), batch: batch(connection, dbVersionSupport), cluster: cluster(connection), collections: collections(connection, dbVersionSupport), diff --git a/src/openapi/types.ts b/src/openapi/types.ts index 4cecdcfc..edf9e939 100644 --- a/src/openapi/types.ts +++ b/src/openapi/types.ts @@ -15,6 +15,7 @@ export type DataObject = definitions['Object']; export type BackupCreateRequest = definitions['BackupCreateRequest']; export type BackupCreateResponse = definitions['BackupCreateResponse']; export type BackupCreateStatusResponse = definitions['BackupCreateStatusResponse']; +export type BackupListResponse = definitions['BackupListResponse']; export type BackupRestoreRequest = definitions['BackupRestoreRequest']; export type BackupRestoreResponse = definitions['BackupRestoreResponse']; export type BackupRestoreStatusResponse = definitions['BackupRestoreStatusResponse']; diff --git a/src/utils/dbVersion.ts b/src/utils/dbVersion.ts index b37c328f..3bdf8c95 100644 --- a/src/utils/dbVersion.ts +++ b/src/utils/dbVersion.ts @@ -163,6 +163,13 @@ export class DbVersionSupport { message: this.errorMessage('Tokenize endpoint stopwords / stopwordPresets', version.show(), '1.37.2'), })); + supportsIncrementalBackups = () => + this.dbVersionProvider.getVersion().then((version) => ({ + version, + supports: version.isAtLeast(1, 37, 0), + message: this.errorMessage('Incremental backups', version.show(), '1.37.0'), + })); + supportsServerSideDefaultVectorIndexType = () => this.dbVersionProvider.getVersion().then((version) => ({ version, From e15b399a6615461f3e454e2b6c1bc9fa2f2c043c Mon Sep 17 00:00:00 2001 From: Duda Nogueira Date: Mon, 3 Aug 2026 18:33:13 -0300 Subject: [PATCH 3/3] test(backup): cover incremental backup creation and version gating Mock tests assert the payload sent on create, the lowercasing of the base ID, the >=1.37.0 gate and the parsing of the base ID in list() and getCreateStatus(). Integration tests create an incremental backup on top of a base backup and restore it. --- test/collections/backup/integration.test.ts | 61 +++++++- test/collections/backup/mock.test.ts | 163 +++++++++++++++++++- 2 files changed, 222 insertions(+), 2 deletions(-) diff --git a/test/collections/backup/integration.test.ts b/test/collections/backup/integration.test.ts index db90067d..4d1dff33 100644 --- a/test/collections/backup/integration.test.ts +++ b/test/collections/backup/integration.test.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ /* eslint-disable no-await-in-loop */ import { afterAll, beforeAll, describe, expect, it, test } from 'vitest'; -import { WeaviateBackupFailed } from '../../../src/errors.js'; +import { WeaviateBackupFailed, WeaviateInvalidInputError } from '../../../src/errors.js'; import weaviate, { Backend, Collection, WeaviateClient } from '../../../src/index.js'; import { requireAtLeast } from '../../../test/version.js'; @@ -258,6 +258,65 @@ describe('Integration testing of backups', () => { }); }); + requireAtLeast(1, 37, 0).describe('incremental backups', () => { + it('creates an incremental backup on top of a base backup', async () => { + const client = await clientPromise; + const collection = await client.collections + .create({ name: 'TestIncrementalBackup' }) + .then((col) => col.data.insert().then(() => col)); + + const base = await client.backup.create({ + backupId: randomBackupId(), + backend: 'filesystem', + includeCollections: [collection.name], + waitForCompletion: true, + }); + expect(base.status).toBe('SUCCESS'); + + // Add data so that the incremental backup has something to pick up + await collection.data.insert(); + + const incremental = await client.backup.create({ + backupId: randomBackupId(), + backend: 'filesystem', + includeCollections: [collection.name], + incrementalBaseBackupId: base.id, + waitForCompletion: true, + }); + expect(incremental.status).toBe('SUCCESS'); + + // Weaviate only reports the base backup ID to root users, so treat it as optional + if (incremental.incrementalBaseBackupId !== undefined) { + expect(incremental.incrementalBaseBackupId).toBe(base.id); + } + + // The incremental backup must be restorable + await client.collections.delete(collection.name); + const restored = await client.backup.restore({ + backupId: incremental.id, + backend: 'filesystem', + includeCollections: [collection.name], + waitForCompletion: true, + }); + expect(restored.status).toBe('SUCCESS'); + await expect(collection.length()).resolves.toBe(2); + + await client.collections.delete(collection.name); + }); + + it('rejects an incremental backup based on itself', async () => { + const client = await clientPromise; + const backupId = randomBackupId(); + await expect( + client.backup.create({ + backupId, + backend: 'filesystem', + incrementalBaseBackupId: backupId, + }) + ).rejects.toThrow(WeaviateInvalidInputError); + }); + }); + function randomBackupId() { return 'backup-id-' + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); } diff --git a/test/collections/backup/mock.test.ts b/test/collections/backup/mock.test.ts index ab4ca53a..d7a1e0e7 100644 --- a/test/collections/backup/mock.test.ts +++ b/test/collections/backup/mock.test.ts @@ -3,7 +3,11 @@ import { Server as HttpServer } from 'http'; import { Server as GrpcServer, createServer } from 'nice-grpc'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { BackupStatus } from '../../../src/collections/backup/types.js'; -import { WeaviateBackupCanceled } from '../../../src/errors.js'; +import { + WeaviateBackupCanceled, + WeaviateInvalidInputError, + WeaviateUnsupportedFeatureError, +} from '../../../src/errors.js'; import weaviate, { WeaviateClient } from '../../../src/index.js'; import { HealthCheckRequest, @@ -13,8 +17,10 @@ import { HealthServiceImplementation, } from '../../../src/proto/google/health/v1/health.js'; import { + BackupCreateRequest, BackupCreateResponse, BackupCreateStatusResponse, + BackupListResponse, BackupRestoreResponse, } from '../../../src/v2/index.js'; @@ -186,3 +192,158 @@ describe('Mock testing of backup cancellation', () => { afterAll(() => mock.close()); }); + +const BASE_BACKUP_ID = 'test-backup-base'; + +/** Mocks the backup endpoints, recording the payload sent by the client on creation. */ +class IncrementalMock { + private grpc: GrpcServer; + private http: HttpServer; + static lastCreateRequest: BackupCreateRequest; + + constructor(grpc: GrpcServer, http: HttpServer) { + this.grpc = grpc; + this.http = http; + } + + public static use = async (version: string, httpPort: number, grpcPort: number) => { + const httpApp = express(); + httpApp.use(express.json()); + httpApp.get('/v1/meta', (req, res) => res.send({ version })); + + httpApp.post(`/v1/backups/${BACKEND}`, (req, res: Response) => { + IncrementalMock.lastCreateRequest = req.body; + res.send({ + id: req.body.id, + backend: BACKEND, + classes: ['Article'], + path: 'path/to/backup', + status: 'STARTED', + }); + }); + httpApp.get(`/v1/backups/${BACKEND}/:id`, (req, res: Response) => + res.send({ + id: req.params.id, + backend: BACKEND, + path: 'path/to/backup', + status: 'SUCCESS', + incremental_base_backup_id: IncrementalMock.lastCreateRequest?.incremental_base_backup_id, + }) + ); + httpApp.get(`/v1/backups/${BACKEND}`, (req, res: Response) => + res.send([ + { id: BASE_BACKUP_ID, classes: ['Article'], status: 'SUCCESS', incremental_base_backup_id: '' }, + { + id: BACKUP_ID, + classes: ['Article'], + status: 'SUCCESS', + incremental_base_backup_id: BASE_BACKUP_ID, + }, + ]) + ); + + const healthMockImpl: HealthServiceImplementation = { + check: (request: HealthCheckRequest): Promise => + Promise.resolve(HealthCheckResponse.create({ status: HealthCheckResponse_ServingStatus.SERVING })), + watch: vi.fn(), + }; + + const grpc = createServer(); + grpc.add(HealthDefinition, healthMockImpl); + + httpApp.on('error', (error) => console.error('HTTP Server Error:', error)); + + await grpc.listen(`localhost:${grpcPort}`); + const http = await httpApp.listen(httpPort); + return new IncrementalMock(grpc, http); + }; + + public close = () => Promise.all([this.http.close(), this.grpc.shutdown()]); +} + +describe('Mock testing of incremental backups', () => { + describe('with a supported Weaviate version', () => { + let client: WeaviateClient; + let mock: IncrementalMock; + + beforeAll(async () => { + mock = await IncrementalMock.use('1.37.0', 8914, 8915); + client = await weaviate.connectToLocal({ port: 8914, grpcPort: 8915 }); + }); + + it('should send the base backup ID when creating an incremental backup', async () => { + const res = await client.backup.create({ + backupId: BACKUP_ID, + backend: BACKEND, + incrementalBaseBackupId: BASE_BACKUP_ID, + waitForCompletion: true, + }); + expect(IncrementalMock.lastCreateRequest.incremental_base_backup_id).toBe(BASE_BACKUP_ID); + expect(res.status).toBe('SUCCESS'); + expect(res.incrementalBaseBackupId).toBe(BASE_BACKUP_ID); + }); + + it('should lowercase the base backup ID', async () => { + await client.backup.create({ + backupId: BACKUP_ID, + backend: BACKEND, + incrementalBaseBackupId: 'Test-Backup-BASE', + waitForCompletion: true, + }); + expect(IncrementalMock.lastCreateRequest.incremental_base_backup_id).toBe(BASE_BACKUP_ID); + }); + + it('should not send the field for a regular backup', async () => { + await client.backup.create({ backupId: BACKUP_ID, backend: BACKEND }); + expect(IncrementalMock.lastCreateRequest.incremental_base_backup_id).toBeUndefined(); + }); + + it('should throw if the base backup is the backup being created', async () => { + const promise = client.backup.create({ + backupId: BACKUP_ID, + backend: BACKEND, + incrementalBaseBackupId: BACKUP_ID, + }); + await expect(promise).rejects.toThrow(WeaviateInvalidInputError); + }); + + it('should surface the base backup ID when listing backups', async () => { + const backups = await client.backup.list(BACKEND); + expect(backups[0].incrementalBaseBackupId).toBeUndefined(); + expect(backups[1].incrementalBaseBackupId).toBe(BASE_BACKUP_ID); + }); + + it('should surface the base backup ID when getting the creation status', async () => { + const status = await client.backup.getCreateStatus({ backupId: BACKUP_ID, backend: BACKEND }); + expect(status.incrementalBaseBackupId).toBeUndefined(); // last create was a regular backup + }); + + afterAll(() => mock.close()); + }); + + describe('with an unsupported Weaviate version', () => { + let client: WeaviateClient; + let mock: IncrementalMock; + + beforeAll(async () => { + mock = await IncrementalMock.use('1.36.0', 8916, 8917); + client = await weaviate.connectToLocal({ port: 8916, grpcPort: 8917 }); + }); + + it('should throw when requesting an incremental backup', async () => { + const promise = client.backup.create({ + backupId: BACKUP_ID, + backend: BACKEND, + incrementalBaseBackupId: BASE_BACKUP_ID, + }); + await expect(promise).rejects.toThrow(WeaviateUnsupportedFeatureError); + }); + + it('should still allow regular backups', async () => { + const res = await client.backup.create({ backupId: BACKUP_ID, backend: BACKEND }); + expect(res.status).toBe('STARTED'); + }); + + afterAll(() => mock.close()); + }); +});