From 2477db4a92ac1ecf3849b3d157d60a184b59079b Mon Sep 17 00:00:00 2001 From: Stepan Arsentjev Date: Wed, 4 Feb 2026 14:25:37 -0800 Subject: [PATCH 1/2] fix: address E2E test review issues - Fix index creation timeout: use polling (90s) instead of fixed 5s wait - Add gitleaks:allow comments to prevent false positives on test API keys - Index creation now properly waits for status.ready=true Addresses review comments from PRs #27, #29 --- e2e/connection-flow.spec.ts | 2 +- e2e/electron.setup.ts | 2 +- e2e/index-collection-management.spec.ts | 19 ++++++++++--------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/e2e/connection-flow.spec.ts b/e2e/connection-flow.spec.ts index 1208b7c..fb39199 100644 --- a/e2e/connection-flow.spec.ts +++ b/e2e/connection-flow.spec.ts @@ -75,7 +75,7 @@ test.describe('E2E-002: Connection Flow Tests', () => { id, name: 'Invalid Key Test', provider: 'pinecone' as const, - apiKey: 'invalid-api-key-12345', + apiKey: 'invalid-api-key-for-tests', // gitleaks:allow } try { diff --git a/e2e/electron.setup.ts b/e2e/electron.setup.ts index 819c5e4..8ddcfa0 100644 --- a/e2e/electron.setup.ts +++ b/e2e/electron.setup.ts @@ -181,7 +181,7 @@ export async function createPineconeTestProfile( ): Promise { const profileId = `test-pinecone-${Date.now()}` const profileName = name || `Test Pinecone ${Date.now()}` - const pineconeKey = apiKey || process.env.PINECONE_API_KEY || 'dummy-key-for-local-testing' + const pineconeKey = apiKey || process.env.PINECONE_API_KEY || 'dummy-key-for-local-testing' // gitleaks:allow await page.evaluate( async ({ id, name, apiKey }) => { diff --git a/e2e/index-collection-management.spec.ts b/e2e/index-collection-management.spec.ts index 69d8990..7c89034 100644 --- a/e2e/index-collection-management.spec.ts +++ b/e2e/index-collection-management.spec.ts @@ -182,16 +182,17 @@ test.describe('E2E-003: Index/Collection Management Tests', () => { await (window as any).electronAPI.pinecone.createIndex(id, params) }, { id: testProfileId, params: createParams }) - // Wait for index to be created and become ready - // Pinecone indexes can take a while to initialize - await page.waitForTimeout(5000) - - // Verify index was created by listing indexes - const indexes = await page.evaluate(async (id) => { - return await (window as any).electronAPI.pinecone.listIndexes(id) - }, testProfileId) + // Poll for index to become ready (can take 30-60+ seconds for serverless) + let createdIndex: any + await expect(async () => { + const indexes = await page.evaluate(async (id) => { + return await (window as any).electronAPI.pinecone.listIndexes(id) + }, testProfileId) + createdIndex = indexes.find((idx: any) => idx.name === testIndexName) + expect(createdIndex).toBeDefined() + expect(createdIndex?.status?.ready).toBe(true) + }).toPass({ timeout: 90000, intervals: [5000] }) - const createdIndex = indexes.find((idx: any) => idx.name === testIndexName) expect(createdIndex).toBeDefined() expect(createdIndex?.dimension).toBe(384) expect(createdIndex?.metric).toBe('cosine') From b9dc850c3a1479598a81a346219e8d36564b70cb Mon Sep 17 00:00:00 2001 From: Stepan Arsentjev Date: Wed, 4 Feb 2026 14:46:01 -0800 Subject: [PATCH 2/2] fix: address remaining E2E review issues - Add teardown in namespace-operations.spec.ts to delete test-created namespaces/vectors - Change vector-browsing.spec.ts to create dedicated test namespace with known metadata instead of reusing existing namespace - Fix forEach callbacks in vector-browsing.spec.ts to use explicit block bodies instead of implicit returns - Use test.describe.serial for stateful index create/delete tests in index-collection-management.spec.ts - Replace fixed sleeps with polling in index-collection-management.spec.ts Co-Authored-By: Claude Sonnet 4.5 --- e2e/index-collection-management.spec.ts | 37 +++++--- e2e/namespace-operations.spec.ts | 45 +++++++++ e2e/vector-browsing.spec.ts | 120 ++++++++++++++---------- 3 files changed, 139 insertions(+), 63 deletions(-) diff --git a/e2e/index-collection-management.spec.ts b/e2e/index-collection-management.spec.ts index 7c89034..fa5fd96 100644 --- a/e2e/index-collection-management.spec.ts +++ b/e2e/index-collection-management.spec.ts @@ -152,7 +152,8 @@ test.describe('E2E-003: Index/Collection Management Tests', () => { } }) - test('should create new index with provider-specific settings', async () => { + test.describe.serial('Index Create/Delete Operations', () => { + test('should create new index with provider-specific settings', async () => { const { page } = electronContext const hasRealApiKey = !!process.env.PINECONE_API_KEY && @@ -196,9 +197,9 @@ test.describe('E2E-003: Index/Collection Management Tests', () => { expect(createdIndex).toBeDefined() expect(createdIndex?.dimension).toBe(384) expect(createdIndex?.metric).toBe('cosine') - }) + }) - test('should delete index (with confirmation flow)', async () => { + test('should delete index (with confirmation flow)', async () => { const { page } = electronContext const hasRealApiKey = !!process.env.PINECONE_API_KEY && @@ -215,24 +216,30 @@ test.describe('E2E-003: Index/Collection Management Tests', () => { return } - // Wait a bit more for index to be fully ready before deletion - await page.waitForTimeout(10000) + // Poll to ensure index is fully ready before deletion + await expect(async () => { + const indexes = await page.evaluate(async (id) => { + return await (window as any).electronAPI.pinecone.listIndexes(id) + }, testProfileId) + const index = indexes.find((idx: any) => idx.name === testIndexName) + expect(index).toBeDefined() + expect(index?.status?.ready).toBe(true) + }).toPass({ timeout: 30000, intervals: [2000] }) // Delete the test index await page.evaluate(async ({ id, name }) => { await (window as any).electronAPI.pinecone.deleteIndex(id, name) }, { id: testProfileId, name: testIndexName }) - // Wait for deletion to complete - await page.waitForTimeout(3000) - - // Verify index was deleted by listing indexes - const indexes = await page.evaluate(async (id) => { - return await (window as any).electronAPI.pinecone.listIndexes(id) - }, testProfileId) - - const deletedIndex = indexes.find((idx: any) => idx.name === testIndexName) - expect(deletedIndex).toBeUndefined() + // Poll to verify index was deleted + await expect(async () => { + const indexes = await page.evaluate(async (id) => { + return await (window as any).electronAPI.pinecone.listIndexes(id) + }, testProfileId) + const deletedIndex = indexes.find((idx: any) => idx.name === testIndexName) + expect(deletedIndex).toBeUndefined() + }).toPass({ timeout: 30000, intervals: [2000] }) + }) }) test('should handle index stats for empty index', async () => { diff --git a/e2e/namespace-operations.spec.ts b/e2e/namespace-operations.spec.ts index 8be81ca..c2cc518 100644 --- a/e2e/namespace-operations.spec.ts +++ b/e2e/namespace-operations.spec.ts @@ -22,6 +22,7 @@ test.describe('E2E-004: Namespace Operations Tests', () => { test.describe('Pinecone Namespace Management', () => { let testProfileId: string let testIndexName: string + const createdNamespaces: string[] = [] test.beforeAll(async () => { const { page } = electronContext @@ -60,6 +61,46 @@ test.describe('E2E-004: Namespace Operations Tests', () => { } }) + test.afterAll(async () => { + const { page } = electronContext + + const hasRealApiKey = !!process.env.PINECONE_API_KEY && + process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' + + if (!hasRealApiKey || !testIndexName || createdNamespaces.length === 0) { + return + } + + // Clean up created namespaces by deleting their vectors + for (const namespace of createdNamespaces) { + try { + // Get all vectors in namespace + const vectors = await page.evaluate(async ({ id, indexName, namespace }) => { + return await (window as any).electronAPI.pinecone.getAllVectors( + id, + indexName, + namespace, + 10000 // high limit to get all vectors + ) + }, { id: testProfileId, indexName: testIndexName, namespace }) + + // Delete each vector + for (const vector of vectors) { + await page.evaluate(async ({ id, indexName, namespace, vectorId }) => { + await (window as any).electronAPI.pinecone.deleteVector(id, { + indexName, + namespace, + id: vectorId, + }) + }, { id: testProfileId, indexName: testIndexName, namespace, vectorId: vector.id }) + } + } catch (error) { + // Ignore errors during cleanup + console.warn(`Failed to clean up namespace ${namespace}:`, error) + } + } + }) + test('should list namespaces in an index via stats', async () => { const { page } = electronContext @@ -154,6 +195,7 @@ test.describe('E2E-004: Namespace Operations Tests', () => { // Create test namespace with a few vectors const testNamespace = `test-ns-${Date.now()}` + createdNamespaces.push(testNamespace) const testVectors = [ { id: `test-vec-1`, @@ -281,6 +323,7 @@ test.describe('E2E-004: Namespace Operations Tests', () => { } const targetNamespace = `cloned-ns-${Date.now()}` + createdNamespaces.push(targetNamespace) // Start cloning operation const clonePromise = page.evaluate(async ({ id, indexName, source, target }) => { @@ -352,6 +395,7 @@ test.describe('E2E-004: Namespace Operations Tests', () => { } const targetNamespace = `progress-test-${Date.now()}` + createdNamespaces.push(targetNamespace) // Set up progress tracking const progressEvents: any[] = [] @@ -435,6 +479,7 @@ test.describe('E2E-004: Namespace Operations Tests', () => { } const targetNamespace = `cancel-test-${Date.now()}` + createdNamespaces.push(targetNamespace) // Set up progress tracking to detect when copying starts await page.evaluate(() => { diff --git a/e2e/vector-browsing.spec.ts b/e2e/vector-browsing.spec.ts index 8d910a3..4bf3ae5 100644 --- a/e2e/vector-browsing.spec.ts +++ b/e2e/vector-browsing.spec.ts @@ -60,57 +60,79 @@ test.describe('E2E-005: Vector Browsing Tests', () => { if (indexes.length > 0) { testIndexName = indexes[0].name - // Get a namespace with vectors for testing - const stats = await page.evaluate(async ({ id, name }) => { - return await (window as any).electronAPI.pinecone.getIndexStats(id, name) - }, { id: testProfileId, name: testIndexName }) - - // Find first namespace with vectors - for (const [name, data] of Object.entries(stats.namespaces as Record)) { - if (data.vectorCount > 0) { - testNamespace = name - break - } + // Always create a dedicated test namespace with known vectors + testNamespace = `test-vectors-${Date.now()}` + const dimension = indexes[0].dimension || 384 + + // Create test vectors with deterministic metadata + const testVectors = Array.from({ length: 5 }, (_, i) => ({ + id: `test-vector-${i}`, + values: Array(dimension).fill(0).map(() => Math.random()), + metadata: { + test: true, + index: i, + description: `Test vector ${i}`, + category: i % 2 === 0 ? 'even' : 'odd', + }, + })) + + for (const vector of testVectors) { + await page.evaluate(async ({ id, indexName, namespace, vector }) => { + await (window as any).electronAPI.pinecone.createVector(id, { + indexName, + namespace, + id: vector.id, + values: vector.values, + metadata: vector.metadata, + }) + }, { + id: testProfileId, + indexName: testIndexName, + namespace: testNamespace, + vector, + }) } - // If no namespace has vectors, create test vectors - if (!testNamespace) { - testNamespace = `test-vectors-${Date.now()}` - const dimension = indexes[0].dimension || 384 - - // Create a few test vectors - const testVectors = Array.from({ length: 5 }, (_, i) => ({ - id: `test-vector-${i}`, - values: Array(dimension).fill(0).map(() => Math.random()), - metadata: { - test: true, - index: i, - description: `Test vector ${i}`, - category: i % 2 === 0 ? 'even' : 'odd', - }, - })) - - for (const vector of testVectors) { - await page.evaluate(async ({ id, indexName, namespace, vector }) => { - await (window as any).electronAPI.pinecone.createVector(id, { - indexName, - namespace, - id: vector.id, - values: vector.values, - metadata: vector.metadata, - }) - }, { - id: testProfileId, - indexName: testIndexName, - namespace: testNamespace, - vector, - }) - } + // Wait for vectors to be indexed + await page.waitForTimeout(3000) + } + } + }) - // Wait for vectors to be indexed - await page.waitForTimeout(3000) - } + test.afterAll(async () => { + const { page } = electronContext + + const hasRealApiKey = !!process.env.PINECONE_API_KEY && + process.env.PINECONE_API_KEY !== 'dummy-key-for-local-testing' + + if (!hasRealApiKey || !testIndexName || !testNamespace) { + return + } + + // Clean up test namespace by deleting all test vectors + try { + const vectors = await page.evaluate(async ({ id, indexName, namespace }) => { + return await (window as any).electronAPI.pinecone.getAllVectors( + id, + indexName, + namespace, + 10000 // high limit to get all vectors + ) + }, { id: testProfileId, indexName: testIndexName, namespace: testNamespace }) + + // Delete each vector + for (const vector of vectors) { + await page.evaluate(async ({ id, indexName, namespace, vectorId }) => { + await (window as any).electronAPI.pinecone.deleteVector(id, { + indexName, + namespace, + id: vectorId, + }) + }, { id: testProfileId, indexName: testIndexName, namespace: testNamespace, vectorId: vector.id }) } + } catch (error) { + // Ignore errors during cleanup + console.warn(`Failed to clean up test namespace ${testNamespace}:`, error) } }) @@ -482,7 +504,9 @@ test.describe('E2E-005: Vector Browsing Tests', () => { const metadataKeys = new Set() vectors.forEach((v: any) => { if (v.metadata) { - Object.keys(v.metadata).forEach(key => metadataKeys.add(key)) + Object.keys(v.metadata).forEach((key) => { + metadataKeys.add(key) + }) } })