Skip to content
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
2 changes: 1 addition & 1 deletion e2e/connection-flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion e2e/electron.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export async function createPineconeTestProfile(
): Promise<string> {
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 }) => {
Expand Down
56 changes: 32 additions & 24 deletions e2e/index-collection-management.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down Expand Up @@ -182,22 +183,23 @@ 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')
})
})

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 &&
Expand All @@ -214,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 () => {
Expand Down
45 changes: 45 additions & 0 deletions e2e/namespace-operations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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`,
Expand Down Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -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[] = []
Expand Down Expand Up @@ -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(() => {
Expand Down
120 changes: 72 additions & 48 deletions e2e/vector-browsing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>)) {
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)
}
})

Expand Down Expand Up @@ -482,7 +504,9 @@ test.describe('E2E-005: Vector Browsing Tests', () => {
const metadataKeys = new Set<string>()
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)
})
}
})

Expand Down