Skip to content

Commit 4de5c8f

Browse files
feat: Introduce local ChromaDB server management for testing
1 parent 23aa500 commit 4de5c8f

9 files changed

Lines changed: 112 additions & 46 deletions

File tree

.github/workflows/test.yml

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,15 @@ jobs:
1414

1515
name: Tests on PHP ${{ matrix.php }} - ${{ matrix.dependency-version }}
1616

17-
services:
18-
chroma-wo-auth:
19-
image: chromadb/chroma:1.0.8
20-
ports:
21-
- 8000:8000
22-
23-
chroma-w-auth:
24-
image: chromadb/chroma:1.0.8
25-
ports:
26-
- 8001:8000
27-
env:
28-
CHROMA_SERVER_AUTHN_CREDENTIALS: 'test-token'
29-
CHROMA_SERVER_AUTHN_PROVIDER: 'chromadb.auth.token_authn.TokenAuthenticationServerProvider'
30-
CHROMA_AUTH_TOKEN_TRANSPORT_HEADER: 'Authorization'
31-
3217
steps:
3318
- name: Checkout
3419
uses: actions/checkout@v3
3520

21+
- name: Install Chroma CLI
22+
run: |
23+
curl -sSL https://raw.githubusercontent.com/chroma-core/chroma/main/rust/cli/install/install.sh | bash
24+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
25+
3626
- name: Cache dependencies
3727
uses: actions/cache@v3
3828
with:

chroma.sqlite3

164 KB
Binary file not shown.

chroma/chroma.sqlite3

172 KB
Binary file not shown.

composer.json

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
{
22
"name": "codewithkyrian/chromadb-php",
33
"description": "A PHP client for the Chroma Open Source Embedding Database",
4-
"keywords": ["chromadb", "php", "embedding", "database", "vectors", "semantic", "search", "chroma", "open-source"],
4+
"keywords": [
5+
"chromadb",
6+
"php",
7+
"embedding",
8+
"database",
9+
"vectors",
10+
"semantic",
11+
"search",
12+
"chroma",
13+
"open-source"
14+
],
515
"type": "library",
616
"license": "MIT",
717
"require": {
@@ -11,13 +21,19 @@
1121
"require-dev": {
1222
"pestphp/pest": "^2.19",
1323
"symfony/var-dumper": "^6.3",
14-
"mockery/mockery": "^1.6"
24+
"mockery/mockery": "^1.6",
25+
"symfony/process": "^7.4"
1526
},
1627
"autoload": {
1728
"psr-4": {
1829
"Codewithkyrian\\ChromaDB\\": "src/"
1930
}
2031
},
32+
"autoload-dev": {
33+
"psr-4": {
34+
"Codewithkyrian\\ChromaDB\\Tests\\": "tests/"
35+
}
36+
},
2137
"authors": [
2238
{
2339
"name": "Kyrian Obikwelu",
@@ -33,4 +49,4 @@
3349
"test": "vendor/bin/pest",
3450
"test:coverage": "XDEBUG_MODE=coverage ./vendor/bin/pest --coverage"
3551
}
36-
}
52+
}

src/Api.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Codewithkyrian\ChromaDB;
66

7+
use Codewithkyrian\ChromaDB\Exceptions\ChromaAuthorizationException;
78
use Codewithkyrian\ChromaDB\Exceptions\ChromaConnectionException;
89
use Codewithkyrian\ChromaDB\Exceptions\ChromaException;
910
use Codewithkyrian\ChromaDB\Models\Collection;
@@ -547,6 +548,13 @@ private function handleChromaApiException(\Exception|ClientExceptionInterface $e
547548
}
548549

549550
if ($e instanceof RequestException) {
551+
if ($e->hasResponse()) {
552+
$statusCode = $e->getResponse()->getStatusCode();
553+
if ($statusCode === 401 || $statusCode === 403) {
554+
throw new ChromaAuthorizationException($e->getMessage(), $statusCode);
555+
}
556+
}
557+
550558
$errorString = $e->getResponse()->getBody()->getContents();
551559

552560
if (preg_match('/(?<={"\"error\"\:\")([^"]*)/', $errorString, $matches)) {

tests/ChromaDB.php

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Codewithkyrian\ChromaDB\Client;
66
use Codewithkyrian\ChromaDB\ChromaDB;
7-
use Codewithkyrian\ChromaDB\Exceptions\ChromaAuthorizationException;
87
use Codewithkyrian\ChromaDB\Exceptions\ChromaConnectionException;
98

109
it('can connect to a normal chroma server', function () {
@@ -22,33 +21,6 @@
2221
expect($client)->toBeInstanceOf(Client::class);
2322
});
2423

25-
// test('can connect to an API token authenticated chroma server', function () {
26-
// $client = ChromaDB::factory()
27-
// ->withPort(8001)
28-
// ->withAuthToken('test-token')
29-
// ->connect();
30-
31-
// expect($client)->toBeInstanceOf(Client::class);
32-
// });
33-
34-
/*
35-
NOTE: Currently token-based authentication is broken in the current ChromaDB versions
36-
37-
it('cannot connect to an API token authenticated chroma server with wrong token', function () {
38-
ChromaDB::factory()
39-
->withPort(8001)
40-
->withAuthToken('wrong-token')
41-
->connect();
42-
})->throws(ChromaAuthorizationException::class);
43-
44-
it('throws exception when connecting to API token authenticated chroma server with no token', function () {
45-
ChromaDB::factory()
46-
->withPort(8001)
47-
->connect();
48-
})->throws(ChromaAuthorizationException::class);
49-
50-
*/
51-
5224
it('throws a connection exception when connecting to a non-existent chroma server', function () {
5325
ChromaDB::factory()
5426
->withHost('http://localhost')

tests/ChromaServer.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace Codewithkyrian\ChromaDB\Tests;
4+
5+
use Symfony\Component\Process\Process;
6+
7+
class ChromaServer
8+
{
9+
private static ?Process $process = null;
10+
11+
public static function start(int $port = 8000): void
12+
{
13+
if (self::$process !== null && self::$process->isRunning()) {
14+
return;
15+
}
16+
17+
if (self::isPortInUse($port)) {
18+
echo "Port $port is already in use. Assuming Chroma is running externally.\n";
19+
return;
20+
}
21+
22+
$command = ['chroma', 'run', '--port', (string)$port];
23+
24+
self::$process = new Process($command, env: [
25+
'IS_PERSISTENT' => false,
26+
'ALLOW_RESET' => true
27+
]);
28+
29+
self::$process->start();
30+
31+
$retries = 20;
32+
while ($retries > 0) {
33+
if (self::isPortInUse($port)) {
34+
return;
35+
}
36+
usleep(500000); // 0.5 seconds
37+
$retries--;
38+
}
39+
40+
throw new \RuntimeException("Failed to start Chroma server on port $port.");
41+
}
42+
43+
public static function stop(): void
44+
{
45+
if (self::$process !== null && self::$process->isRunning()) {
46+
self::$process->stop();
47+
self::$process = null;
48+
}
49+
}
50+
51+
private static function isPortInUse(int $port): bool
52+
{
53+
$connection = @fsockopen('localhost', $port);
54+
55+
if (is_resource($connection)) {
56+
fclose($connection);
57+
return true;
58+
}
59+
60+
return false;
61+
}
62+
63+
}

tests/Client.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
use Codewithkyrian\ChromaDB\Models\Collection;
1414

1515
beforeEach(function () {
16+
// $this->chromaServer->start();
17+
1618
$this->client = ChromaDB::factory()
1719
->withDatabase('test_database')
1820
->withTenant('test_tenant')

tests/Pest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
use Codewithkyrian\ChromaDB\ChromaDB;
4+
use Codewithkyrian\ChromaDB\Tests\ChromaServer;
5+
6+
uses()
7+
->beforeAll(function () {
8+
ChromaServer::start();
9+
// ChromaDB::reset();
10+
})
11+
->afterAll(function () {
12+
// ChromaDB::reset();
13+
ChromaServer::stop();
14+
})
15+
->in(__DIR__);

0 commit comments

Comments
 (0)