Skip to content

Commit 9afa8a9

Browse files
committed
fix remote test runs in github actions
1 parent edf3fa5 commit 9afa8a9

File tree

4 files changed

+114
-103
lines changed

4 files changed

+114
-103
lines changed

.github/workflows/test.yml

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,14 @@ jobs:
66
test:
77
runs-on: ubuntu-latest
88
env:
9-
MONGODB_URI: ${{ secrets.MONGODB_URI }}
10-
EMBEDDING_PROVIDER: ${{ secrets.EMBEDDING_PROVIDER }}
11-
EMBEDDING_API_KEY: ${{ secrets.EMBEDDING_API_KEY }}
9+
MONGODB_URI: mongodb://localhost:27017/test
10+
EMBEDDING_PROVIDER: openai
11+
EMBEDDING_API_KEY: test-key-123
12+
NODE_ENV: test
1213
steps:
13-
- name: Checkout code
14-
uses: actions/checkout@v3
15-
16-
- name: Setup Node.js
17-
uses: actions/setup-node@v3
14+
- uses: actions/checkout@v3
15+
- uses: actions/setup-node@v3
1816
with:
1917
node-version: 18
20-
21-
- name: Install dependencies
22-
run: npm install
23-
24-
- name: Debug Environment Variables
25-
run: |
26-
echo "Embedding Provider: $EMBEDDING_PROVIDER"
27-
echo "API Key Length: ${#EMBEDDING_API_KEY}"
28-
env:
29-
EMBEDDING_API_KEY: ${{ secrets.EMBEDDING_API_KEY }}
30-
31-
- name: Run tests
32-
run: npm test
18+
- run: npm install
19+
- run: npm test

src/providers/BaseEmbeddingProvider.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// src/providers/BaseEmbeddingProvider.js
12
import debug from 'debug';
23

34
const log = debug('mongodb-rag:embedding');
@@ -11,9 +12,13 @@ class BaseEmbeddingProvider {
1112
...options
1213
};
1314

15+
if (!this.options.apiKey) {
16+
throw new Error('API key is required');
17+
}
18+
1419
this.rateLimiter = {
1520
tokens: 60,
16-
refillRate: 60, // tokens per minute
21+
refillRate: 60,
1722
lastRefill: Date.now()
1823
};
1924
}
@@ -23,23 +28,35 @@ class BaseEmbeddingProvider {
2328
texts = [texts];
2429
}
2530

31+
if (texts.length === 0) {
32+
return [];
33+
}
34+
2635
const batches = this._createBatches(texts);
2736
const results = [];
2837

2938
for (const batch of batches) {
3039
await this._waitForRateLimit();
3140

3241
let retries = 0;
42+
let lastError = null;
43+
3344
while (retries < this.options.maxRetries) {
3445
try {
3546
const embeddings = await this._embedBatch(batch);
3647
results.push(...embeddings);
3748
break;
3849
} catch (error) {
3950
retries++;
51+
lastError = error;
52+
4053
if (retries === this.options.maxRetries) {
41-
throw new Error(`Failed to get embeddings after ${retries} retries: ${error.message}`);
54+
const errorMessage = `Failed to get embeddings after ${retries} retries`;
55+
const details = lastError?.message || 'Unknown error';
56+
log(`${errorMessage}: ${details}`);
57+
throw new Error(`${errorMessage}: ${details}`);
4258
}
59+
4360
log(`Retry ${retries} after error: ${error.message}`);
4461
await this._sleep(this.options.retryDelay * retries);
4562
}

src/providers/providers.test.js

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,71 @@
1-
// __tests__/providers.test.js
2-
import { jest } from "@jest/globals";
3-
import dotenv from "dotenv";
4-
dotenv.config();
1+
// src/providers/providers.test.js
2+
import { jest, describe, expect, test, beforeEach } from '@jest/globals';
3+
import OpenAIEmbeddingProvider from '../../src/providers/OpenAIEmbeddingProvider.js';
54

6-
import OpenAIEmbeddingProvider from "../../src/providers/OpenAIEmbeddingProvider.js";
7-
import DeepSeekEmbeddingProvider from "../../src/providers/DeepSeekEmbeddingProvider.js";
8-
9-
// ✅ Mock `axios` correctly
10-
jest.unstable_mockModule("axios", async () => ({
5+
// Mock axios
6+
jest.mock('axios', () => ({
117
default: {
128
create: jest.fn(() => ({
13-
post: jest.fn().mockImplementation(async (url, body) => {
14-
if (url.includes("openai.com")) {
15-
return { data: { data: [{ embedding: Array(1536).fill(0.1) }] } };
16-
} else if (url.includes("deepseek.com")) {
17-
return { data: { data: [{ embedding: Array(1536).fill(Math.random() * 0.02 - 0.01) }] } }; // ✅ Returns random small floats
9+
post: jest.fn().mockResolvedValue({
10+
data: {
11+
data: [{ embedding: Array(1536).fill(0.1) }]
1812
}
19-
throw new Error("Unknown API endpoint");
20-
}),
21-
})),
22-
},
13+
})
14+
}))
15+
}
2316
}));
2417

25-
describe("Embedding Providers", () => {
18+
describe('Embedding Providers', () => {
2619
let provider;
20+
const mockApiKey = 'test-key-123';
2721

2822
beforeEach(() => {
29-
const providerType = process.env.EMBEDDING_PROVIDER || "openai";
30-
const apiKey = process.env.EMBEDDING_API_KEY;
31-
32-
switch (providerType) {
33-
case "openai":
34-
provider = new OpenAIEmbeddingProvider({ apiKey });
35-
break;
36-
case "deepseek":
37-
provider = new DeepSeekEmbeddingProvider({ apiKey });
38-
break;
39-
default:
40-
throw new Error(`Unsupported provider for embeddings: ${providerType}`);
41-
}
23+
// Reset mocks
24+
jest.clearAllMocks();
25+
26+
provider = new OpenAIEmbeddingProvider({
27+
apiKey: mockApiKey,
28+
model: 'text-embedding-3-small',
29+
dimensions: 1536
30+
});
4231
});
4332

44-
test("should get embeddings from the selected provider", async () => {
45-
const result = await provider.getEmbeddings(["test"]);
46-
47-
expect(result).toHaveLength(1); // ✅ Ensures a single embedding
48-
expect(result[0]).toHaveLength(1536); // ✅ Ensures correct vector size
33+
test('should initialize with proper configuration', () => {
34+
expect(provider.apiKey).toBe(mockApiKey);
35+
expect(provider.model).toBe('text-embedding-3-small');
36+
});
4937

50-
// ✅ Instead of checking for a fixed number, check for numerical range
51-
expect(typeof result[0][0]).toBe("number");
38+
test('should get embeddings from the OpenAI provider', async () => {
39+
const texts = ['test text'];
40+
const result = await provider.getEmbeddings(texts);
41+
42+
expect(result).toHaveLength(1);
43+
expect(result[0]).toHaveLength(1536);
44+
expect(typeof result[0][0]).toBe('number');
5245
expect(result[0][0]).toBeGreaterThanOrEqual(-1);
5346
expect(result[0][0]).toBeLessThanOrEqual(1);
54-
}, 15000);
55-
});
47+
});
48+
49+
test('should handle empty input', async () => {
50+
const result = await provider.getEmbeddings([]);
51+
expect(result).toHaveLength(0);
52+
});
53+
54+
test('should handle batch processing', async () => {
55+
const texts = Array(5).fill('test text');
56+
const result = await provider.getEmbeddings(texts);
57+
expect(result).toHaveLength(5);
58+
expect(result[0]).toHaveLength(1536);
59+
});
60+
61+
test('should respect batch size limits', async () => {
62+
const provider = new OpenAIEmbeddingProvider({
63+
apiKey: mockApiKey,
64+
batchSize: 2
65+
});
66+
67+
const texts = Array(5).fill('test text');
68+
const result = await provider.getEmbeddings(texts);
69+
expect(result).toHaveLength(5);
70+
});
71+
});

tests/setup.js

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,51 +6,42 @@ dotenv.config();
66
// Make jest available globally
77
global.jest = jest;
88

9-
// Mock MongoDB operations
10-
const mockCollection = {
11-
createSearchIndex: jest.fn().mockImplementation(async () => ({ name: 'vector_index' })),
12-
indexes: jest.fn().mockImplementation(async () => [{ name: 'vector_index', type: 'search' }]),
13-
listSearchIndexes: jest.fn().mockImplementation(() => ({
14-
toArray: async () => []
15-
})),
16-
createIndex: jest.fn().mockImplementation(async () => 'index_created'),
17-
listIndexes: jest.fn().mockImplementation(() => ({
18-
toArray: async () => []
19-
})),
20-
deleteMany: jest.fn().mockImplementation(async () => ({ deletedCount: 0 }))
21-
};
22-
23-
const mockDb = {
24-
collection: jest.fn().mockReturnValue(mockCollection)
25-
};
26-
27-
const mockClient = {
28-
connect: jest.fn().mockImplementation(async () => undefined),
29-
db: jest.fn().mockReturnValue(mockDb),
30-
close: jest.fn().mockImplementation(async () => undefined),
31-
topology: {
32-
isConnected: jest.fn().mockReturnValue(true)
33-
}
34-
};
9+
// Set default test environment variables if not provided
10+
process.env.EMBEDDING_PROVIDER = process.env.EMBEDDING_PROVIDER || 'openai';
11+
process.env.EMBEDDING_API_KEY = process.env.EMBEDDING_API_KEY || 'test-key-123';
12+
process.env.MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/test';
3513

14+
// Mock MongoDB client
3615
jest.mock('mongodb', () => ({
37-
MongoClient: jest.fn().mockImplementation(() => mockClient)
16+
MongoClient: jest.fn().mockImplementation(() => ({
17+
connect: jest.fn().mockResolvedValue(undefined),
18+
db: jest.fn().mockReturnValue({
19+
collection: jest.fn().mockReturnValue({
20+
createSearchIndex: jest.fn().mockResolvedValue({ name: 'vector_index' }),
21+
indexes: jest.fn().mockResolvedValue([{ name: 'vector_index' }]),
22+
listSearchIndexes: jest.fn().mockReturnValue({
23+
toArray: jest.fn().mockResolvedValue([])
24+
})
25+
})
26+
}),
27+
close: jest.fn().mockResolvedValue(undefined)
28+
}))
3829
}));
3930

40-
// Mock chalk for cleaner test output
41-
jest.mock('chalk', () => ({
42-
cyan: { bold: jest.fn(str => str) },
43-
yellow: jest.fn(str => str),
44-
red: jest.fn(str => str),
45-
green: jest.fn(str => str),
46-
blue: jest.fn(str => str),
47-
gray: jest.fn(str => str),
48-
bold: jest.fn(str => str),
49-
whiteBright: jest.fn(str => str),
50-
magenta: jest.fn(str => str)
31+
// Mock axios
32+
jest.mock('axios', () => ({
33+
default: {
34+
create: jest.fn(() => ({
35+
post: jest.fn().mockResolvedValue({
36+
data: {
37+
data: [{ embedding: Array(1536).fill(0.1) }]
38+
}
39+
})
40+
}))
41+
}
5142
}));
5243

53-
// Set a longer default timeout for all tests
44+
// Set longer timeout for all tests
5445
jest.setTimeout(30000);
5546

5647
// Clear all mocks after each test

0 commit comments

Comments
 (0)