diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6333ab2..15ffbaa 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -16,6 +16,8 @@ env: DENSE_EMBEDDING_UPSTASH_VECTOR_REST_TOKEN: ${{ secrets.DENSE_EMBEDDING_UPSTASH_VECTOR_REST_TOKEN }} SPARSE_UPSTASH_VECTOR_REST_URL: ${{ secrets.SPARSE_UPSTASH_VECTOR_REST_URL }} SPARSE_UPSTASH_VECTOR_REST_TOKEN: ${{ secrets.SPARSE_UPSTASH_VECTOR_REST_TOKEN }} + HYBRID_UPSTASH_VECTOR_REST_URL: ${{ secrets.HYBRID_UPSTASH_VECTOR_REST_URL }} + HYBRID_UPSTASH_VECTOR_REST_TOKEN: ${{ secrets.HYBRID_UPSTASH_VECTOR_REST_TOKEN }} jobs: test: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a81ea2a..92e9f88 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -52,5 +52,8 @@ + + + diff --git a/tests/Concerns/GeneratesVectors.php b/tests/Concerns/GeneratesVectors.php index edaaebd..30651c6 100644 --- a/tests/Concerns/GeneratesVectors.php +++ b/tests/Concerns/GeneratesVectors.php @@ -2,4 +2,16 @@ namespace Upstash\Vector\Tests\Concerns; -trait GeneratesVectors {} +trait GeneratesVectors +{ + protected function generateVector(int $dimensions): array + { + $vector = []; + + for ($i = 0; $i < $dimensions; $i++) { + $vector[] = random_int(0, 100) / 100; + } + + return $vector; + } +} diff --git a/tests/Concerns/UsesHybridIndex.php b/tests/Concerns/UsesHybridIndex.php new file mode 100644 index 0000000..2cae894 --- /dev/null +++ b/tests/Concerns/UsesHybridIndex.php @@ -0,0 +1,33 @@ +index = new Index( + url: getenv('HYBRID_UPSTASH_VECTOR_REST_URL'), + token: getenv('HYBRID_UPSTASH_VECTOR_REST_TOKEN'), + ); + + $this->namespace = $this->index->namespace(bin2hex(random_bytes(32))); + } + + public function tearDown(): void + { + $this->namespace->delete(); + + parent::tearDown(); + } +} diff --git a/tests/Hybrid/Operations/QueryDataTest.php b/tests/Hybrid/Operations/QueryDataTest.php new file mode 100644 index 0000000..a940e21 --- /dev/null +++ b/tests/Hybrid/Operations/QueryDataTest.php @@ -0,0 +1,43 @@ +namespace->upsertDataMany([ + new DataUpsert( + id: '1', + data: 'The capital of Japan is Tokyo', + ), + new DataUpsert( + id: '2', + data: 'The capital of France is Paris', + ), + new DataUpsert( + id: '3', + data: 'The capital of Germany is Berlin', + ), + ]); + + $this->waitForIndex($this->namespace); + + $results = $this->namespace->queryData(new DataQuery( + data: 'capital of France', + topK: 1, + )); + + $this->assertCount(1, $results); + $this->assertEquals('2', $results[0]->id); + } +} diff --git a/tests/Hybrid/Operations/QueryVectorsTest.php b/tests/Hybrid/Operations/QueryVectorsTest.php new file mode 100644 index 0000000..b1f74d6 --- /dev/null +++ b/tests/Hybrid/Operations/QueryVectorsTest.php @@ -0,0 +1,95 @@ +generateVector(384); + + $this->namespace->upsert(new VectorUpsert( + id: '1', + vector: $vector, + sparseVector: new SparseVector( + indices: [1, 2, 3], + values: [5, 6, 7], + ), + )); + + $this->waitForIndex($this->namespace); + + $vectors = $this->namespace->query(new VectorQuery( + vector: $vector, + sparseVector: new SparseVector( + indices: [1, 2, 3], + values: [5, 6, 7], + ), + topK: 1, + )); + + $this->assertCount(1, $vectors); + $this->assertSame('1', $vectors[0]->id); + } + + public function test_query_with_no_vectors(): void + { + $this->namespace->upsert(new VectorUpsert( + id: '1', + vector: $this->generateVector(384), + sparseVector: new SparseVector( + indices: [1, 2, 3], + values: [5, 6, 7], + ), + )); + + $this->waitForIndex($this->namespace); + + $vectors = $this->namespace->query(new VectorQuery( + sparseVector: new SparseVector( + indices: [1, 2, 3], + values: [5, 6, 7], + ), + topK: 1, + )); + + $this->assertCount(1, $vectors); + $this->assertSame('1', $vectors[0]->id); + } + + public function test_query_with_no_sparse_vectors(): void + { + $vector = $this->generateVector(384); + + $this->namespace->upsert(new VectorUpsert( + id: '1', + vector: $vector, + sparseVector: new SparseVector( + indices: [1, 2, 3], + values: [5, 6, 7], + ), + )); + + $this->waitForIndex($this->namespace); + + $vectors = $this->namespace->query(new VectorQuery( + vector: $vector, + topK: 1, + )); + + $this->assertCount(1, $vectors); + $this->assertSame('1', $vectors[0]->id); + } +} diff --git a/tests/Hybrid/Operations/UpsertDataTest.php b/tests/Hybrid/Operations/UpsertDataTest.php new file mode 100644 index 0000000..cac2708 --- /dev/null +++ b/tests/Hybrid/Operations/UpsertDataTest.php @@ -0,0 +1,43 @@ +namespace->upsertData(new DataUpsert( + id: '1', + data: 'The capital of Japan is Tokyo', + )); + + $this->waitForIndex($this->namespace); + + $info = $this->namespace->getNamespaceInfo(); + + $this->assertSame(1, $info->vectorCount); + } + + public function test_upsert_many_data(): void + { + $this->namespace->upsertDataMany([ + new DataUpsert(id: '1', data: 'The capital of Japan is Tokyo'), + new DataUpsert(id: '2', data: 'The capital of France is Paris'), + new DataUpsert(id: '3', data: 'The capital of Germany is Berlin'), + ]); + + $this->waitForIndex($this->namespace); + + $info = $this->namespace->getNamespaceInfo(); + + $this->assertSame(3, $info->vectorCount); + } +} diff --git a/tests/Hybrid/Operations/UpsertVectorTest.php b/tests/Hybrid/Operations/UpsertVectorTest.php new file mode 100644 index 0000000..3914475 --- /dev/null +++ b/tests/Hybrid/Operations/UpsertVectorTest.php @@ -0,0 +1,89 @@ +namespace->upsert(new VectorUpsert( + id: '1', + vector: $this->generateVector(384), + sparseVector: new SparseVector( + indices: [1, 2, 3], + values: [5, 6, 7], + ), + )); + + $this->waitForIndex($this->namespace); + + $info = $this->namespace->getNamespaceInfo(); + + $this->assertSame(1, $info->vectorCount); + } + + public function test_upsert_without_sparse_vector_throws_exception(): void + { + $this->expectException(OperationFailedException::class); + $this->expectExceptionMessage('This index requires sparse vectors'); + + $this->namespace->upsert(new VectorUpsert( + id: '1', + vector: $this->generateVector(384), + )); + } + + public function test_upsert_without_vector_throws_exception(): void + { + $this->expectException(OperationFailedException::class); + $this->expectExceptionMessage('This index requires dense vectors'); + + $this->namespace->upsert(new VectorUpsert( + id: '1', + sparseVector: new SparseVector( + indices: [1, 2, 3], + values: [5, 6, 7], + ), + )); + } + + public function test_upsert_many_vectors(): void + { + $this->namespace->upsertMany([ + new VectorUpsert( + id: '1', + vector: $this->generateVector(384), + sparseVector: new SparseVector( + indices: [1, 2, 3], + values: [5, 6, 7], + ), + ), + new VectorUpsert( + id: '2', + vector: $this->generateVector(384), + sparseVector: new SparseVector( + indices: [4, 5, 6], + values: [8, 9, 10], + ), + ), + ]); + + $this->waitForIndex($this->namespace); + + $info = $this->namespace->getNamespaceInfo(); + + $this->assertSame(2, $info->vectorCount); + } +}