diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index dacfbe0f..74abb492 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -24,12 +24,21 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false - max-parallel: 1 + max-parallel: 2 matrix: - jest_env: ['node', 'edge'] pinecone_env: - prod # - staging + config: + [ + { runner: 'npm', jest_env: 'node' }, + { runner: 'npm', jest_env: 'edge' }, + { runner: 'bun', jest_env: 'node', bun_version: '1.0.0' }, + { runner: 'bun', jest_env: 'node', bun_version: '1.0.5' }, + { runner: 'bun', jest_env: 'node', bun_version: '1.0.10' }, + { runner: 'bun', jest_env: 'node', bun_version: '1.0.15' }, + { runner: 'bun', jest_env: 'node', bun_version: '1.0.20' }, + ] steps: - name: Checkout @@ -38,21 +47,49 @@ jobs: - name: Setup uses: ./.github/actions/setup - - name: Run integration tests (prod) + - name: Setup bun + if: matrix.config.bun_version + uses: oven-sh/setup-bun@v1 + with: + bun-version: ${{ matrix.config.bun_version }} + + - name: Run integration tests (Prod) if: matrix.pinecone_env == 'prod' env: CI: true PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }} - PINECONE_CONTROLLER_HOST: 'https://api.pinecone.io' - run: npm run test:integration:${{ matrix.jest_env }} + run: ${{ matrix.config.runner }} run test:integration:${{ matrix.config.jest_env }} - - name: Run integration tests (staging) + - name: Run integration tests (Staging) if: matrix.pinecone_env == 'staging' env: CI: true - PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY_STAGING }} + PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }} PINECONE_CONTROLLER_HOST: 'https://api-staging.pinecone.io' - run: npm run test:integration:${{ matrix.jest_env }} + run: ${{ matrix.config.runner }} run test:integration:${{ matrix.config.jest_env }} + + integration-test-cleanup: + name: Clean up integration tests + if: always() + needs: [integration-tests] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18.x + registry-url: 'https://registry.npmjs.org' + - name: Install npm packages + run: | + npm install --ignore-scripts + shell: bash + - name: Run integration cleanup command + env: + CI: true + PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }} + run: npm run test:integration:cleanup typescript-compilation-tests: name: TS compile diff --git a/jest.config.js b/jest.config.js index 5c19dbef..a7e54cc6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,7 +12,7 @@ module.exports = { }, transformIgnorePatterns: ['/node_modules/'], testPathIgnorePatterns: ['src/integration'], - testTimeout: 100000, + testTimeout: 150000, verbose: true, detectOpenHandles: false, collectCoverageFrom: [ diff --git a/src/integration/data/delete.test.ts b/src/integration/data/delete.test.ts index 9458eaaa..c2e6be57 100644 --- a/src/integration/data/delete.test.ts +++ b/src/integration/data/delete.test.ts @@ -1,9 +1,9 @@ import { Pinecone, Index } from '../../index'; import { + assertWithRetries, randomString, generateRecords, INDEX_NAME, - sleep, waitUntilRecordsReady, } from '../test-helpers'; @@ -49,7 +49,7 @@ describe('delete', () => { await ns.upsert(recordToUpsert); // Await record freshness, and check record upserted - let stats = await waitUntilRecordsReady(ns, namespace, recordIds); + const stats = await waitUntilRecordsReady(ns, namespace, recordIds); if (stats.namespaces) { expect(stats.namespaces[namespace].recordCount).toEqual(1); } else { @@ -57,31 +57,31 @@ describe('delete', () => { } // Look more closely at one of the records to make sure values set - const fetchResult = await ns.fetch(['0']); - const records = fetchResult.records; - if (records) { - expect(records['0'].id).toEqual('0'); - expect(records['0'].values.length).toEqual(5); - } else { - fail( - 'Did not find expected records. Fetch result was ' + - JSON.stringify(fetchResult) - ); - } + const fetchAssertions = [ + (results) => { + if (results.records) { + expect(results.records['0'].id).toEqual('0'); + expect(results.records['0'].values.length).toEqual(5); + } else { + fail( + 'Did not find expected records. Fetch result was ' + + JSON.stringify(results) + ); + } + }, + ]; + assertWithRetries(() => ns.fetch(['0']), fetchAssertions); // Try deleting the record await ns.deleteOne('0'); - await sleep(3000); // Verify the record is removed - stats = await ns.describeIndexStats(); - if (stats.namespaces) { - expect(stats.namespaces[namespace]).toBeUndefined(); - } else { - // no-op. This shouldn't actually happen unless there - // are leftover namespaces from previous runs that - // failed or stopped without proper cleanup. - } + const deleteAssertions = [ + (stats) => { + expect(stats.namespaces[namespace]).toBeUndefined(); + }, + ]; + assertWithRetries(() => ns.describeIndexStats(), deleteAssertions); }); test('verify deleteMany with ids', async () => { @@ -95,7 +95,7 @@ describe('delete', () => { await ns.upsert(recordsToUpsert); // Await record freshness, and check records upserted - let stats = await waitUntilRecordsReady(ns, namespace, recordIds); + const stats = await waitUntilRecordsReady(ns, namespace, recordIds); if (stats.namespaces) { expect(stats.namespaces[namespace].recordCount).toEqual(3); } else { @@ -103,55 +103,68 @@ describe('delete', () => { } // Look more closely at one of the records to make sure values set - const fetchResult = await ns.fetch(['0']); - const records = fetchResult.records; - if (records) { - expect(records['0'].id).toEqual('0'); - expect(records['0'].values.length).toEqual(5); - } else { - fail( - 'Did not find expected records. Fetch result was ' + - JSON.stringify(fetchResult) - ); - } + const fetchAssertions = [ + (results) => { + if (results.records) { + expect(results.records['0'].id).toEqual('0'); + expect(results.records['0'].values.length).toEqual(5); + } else { + fail( + 'Did not find expected records. Fetch result was ' + + JSON.stringify(results) + ); + } + }, + ]; + assertWithRetries(() => ns.fetch(['0']), fetchAssertions); // Try deleting 2 of 3 records await ns.deleteMany(['0', '2']); - await sleep(3000); - stats = await ns.describeIndexStats(); - if (stats.namespaces) { - expect(stats.namespaces[namespace].recordCount).toEqual(1); - } else { - fail( - 'Expected namespaces to be defined (second call). Stats were ' + - JSON.stringify(stats) - ); - } + + const deleteAssertions = [ + (stats) => { + if (stats.namespaces) { + expect(stats.namespaces[namespace].recordCount).toEqual(1); + } else { + fail( + 'Expected namespaces to be defined (second call). Stats were ' + + JSON.stringify(stats) + ); + } + }, + ]; + assertWithRetries(() => ns.describeIndexStats(), deleteAssertions); // Check that record id='1' still exists - const fetchResult2 = await ns.fetch(['1']); - const records2 = fetchResult2.records; - if (records2) { - expect(records2['1']).not.toBeUndefined(); - } else { - fail( - 'Expected record 2 to be defined. Fetch result was ' + - JSON.stringify(fetchResult2) - ); - } + const fetchAssertions2 = [ + (results) => { + if (results.records2) { + expect(results.records2['1']).not.toBeUndefined(); + } else { + fail( + 'Expected record 2 to be defined. Fetch result was ' + + JSON.stringify(results) + ); + } + }, + ]; + assertWithRetries(() => ns.fetch(['1']), fetchAssertions2); // deleting non-existent records should not throw await ns.deleteMany(['0', '1', '2', '3']); - await sleep(3000); // Verify all are now removed - stats = await ns.describeIndexStats(); - if (stats.namespaces) { - expect(stats.namespaces[namespace]).toBeUndefined(); - } else { - // no-op. This shouldn't actually happen unless there - // are leftover namespaces from previous runs that - // failed or stopped without proper cleanup. - } + const deleteAssertions2 = [ + (stats) => { + if (stats.namespaces) { + expect(stats.namespaces[namespace]).toBeUndefined(); + } else { + // no-op. This shouldn't actually happen unless there + // are leftover namespaces from previous runs that + // failed or stopped without proper cleanup. + } + }, + ]; + assertWithRetries(() => ns.describeIndexStats(), deleteAssertions2); }); }); diff --git a/src/integration/data/query.test.ts b/src/integration/data/query.test.ts index 6e671d53..bf476624 100644 --- a/src/integration/data/query.test.ts +++ b/src/integration/data/query.test.ts @@ -128,4 +128,26 @@ describe('query', () => { assertions ); }); + + test('query with includeValues: true', async () => { + const recordsToUpsert = generateRecords(5, 3); + expect(recordsToUpsert).toHaveLength(3); + + const queryVec = Array.from({ length: 5 }, () => Math.random()); + + const assertions = [ + (results) => expect(results.matches).toBeDefined(), + (results) => expect(results.matches?.length).toEqual(2), + ]; + await assertWithRetries( + () => + ns.query({ + vector: queryVec, + topK: 2, + includeValues: true, + includeMetadata: true, + }), + assertions + ); + }); });