From 3d369a4786d5494d6063d92cfaba67ee7e659add Mon Sep 17 00:00:00 2001 From: Sergey Avseyev Date: Wed, 2 Oct 2024 09:57:20 -0700 Subject: [PATCH] Fix tests --- .github/workflows/tests.yml | 14 ++- bin/test.rb | 5 + tests/Helpers/ConsistencyUtils.php | 164 ++++++++++++++++++++-------- tests/Helpers/CouchbaseTestCase.php | 15 ++- tests/KeyValueGetReplicaTest.php | 4 +- tests/KeyValueLookupInTest.php | 14 ++- tests/QueryIndexManagerTest.php | 101 ++++++++++++----- tests/SearchIndexManagerTest.php | 11 +- tests/SearchTest.php | 119 ++++++++++++++------ tests/ViewTest.php | 75 ++++++++++--- tests/beer-data.json | 36 +++--- tests/beer-search.json | 2 +- 12 files changed, 398 insertions(+), 162 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4c5a6c03..b10ef5e3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -316,8 +316,8 @@ jobs: - nts - zts server: - - 7.6.0 - - 7.2.4 + - 7.6.3 + - 7.2.5 - 7.1.6 - 7.0.5 steps: @@ -331,7 +331,7 @@ jobs: - name: Install cbdinocluster run: | mkdir -p "$HOME/bin" - curl -L -o "$HOME/bin/cbdinocluster" https://github.com/couchbaselabs/cbdinocluster/releases/download/v0.0.35/cbdinocluster-linux + curl -L -o "$HOME/bin/cbdinocluster" https://github.com/couchbaselabs/cbdinocluster/releases/download/v0.0.52/cbdinocluster-linux-amd64 chmod a+x "$HOME/bin/cbdinocluster" echo "$HOME/bin" >> $GITHUB_PATH - name: Initialize cbdinocluster @@ -341,7 +341,11 @@ jobs: env: CLUSTERCONFIG: | nodes: - - count: 2 + - count: 1 + version: ${{ matrix.server }} + services: + - kv + - count: 1 version: ${{ matrix.server }} services: - kv @@ -358,7 +362,7 @@ jobs: run: | CLUSTER_ID=$(cbdinocluster -v allocate --def="${CLUSTERCONFIG}") CONNECTION_STRING=$(cbdinocluster -v connstr "${CLUSTER_ID}") - cbdinocluster -v buckets add ${CLUSTER_ID} default --ram-quota-mb=100 --flush-enabled=true + cbdinocluster -v buckets add ${CLUSTER_ID} default --ram-quota-mb=100 --flush-enabled=true --num-replicas=1 cbdinocluster -v buckets load-sample ${CLUSTER_ID} travel-sample echo "CLUSTER_ID=${CLUSTER_ID}" >> "$GITHUB_ENV" echo "TEST_CONNECTION_STRING=${CONNECTION_STRING}?dump_configuration=true" >> "$GITHUB_ENV" diff --git a/bin/test.rb b/bin/test.rb index 3a382e35..bcfd382e 100755 --- a/bin/test.rb +++ b/bin/test.rb @@ -68,6 +68,11 @@ def capture(*args) output end +if ENV["CB_VALGRIND"] + ENV["USE_ZEND_ALLOC"] = 0 + ENV["ZEND_DONT_UNLOAD_MODULES"] = 1 +end + project_root = File.expand_path(File.join(__dir__, "..")) build_root = File.join(project_root, "build") diff --git a/tests/Helpers/ConsistencyUtils.php b/tests/Helpers/ConsistencyUtils.php index d12a4106..f3f8e496 100644 --- a/tests/Helpers/ConsistencyUtils.php +++ b/tests/Helpers/ConsistencyUtils.php @@ -4,8 +4,13 @@ use Exception; -const MANAGEMENT_PORT = 8091; -const VIEWS_PORT = 8092; +const PORTS = [ + 'mgmt' => 8091, + 'views' => 8092, + 'n1ql' => 8093, + 'fts' => 8094, +]; + class ConsistencyUtils { private string $hostname; @@ -33,13 +38,13 @@ public function resourceIsDropped(int $statusCode): bool */ public function waitUntilUserPresent(string $userName, string $domain = 'local'): void { - printf("waiting until user %s present on all nodes\n", $userName); + fprintf(STDERR, "waiting until user %s present on all nodes\n", $userName); $this->waitUntilAllNodesMatchPredicate( "/settings/rbac/users/$domain/$userName", [$this, 'resourceIsPresent'], "User $userName is not present on all nodes", true, - MANAGEMENT_PORT + 'mgmt' ); } @@ -48,13 +53,13 @@ public function waitUntilUserPresent(string $userName, string $domain = 'local') */ public function waitUntilUserDropped(string $userName, string $domain = 'local'): void { - printf("waiting until user %s dropped on all nodes\n", $userName); + fprintf(STDERR, "waiting until user %s dropped on all nodes\n", $userName); $this->waitUntilAllNodesMatchPredicate( "/settings/rbac/users/$domain/$userName", [$this, 'resourceIsDropped'], "User $userName is not dropped on all nodes", true, - MANAGEMENT_PORT + 'mgmt' ); } @@ -63,103 +68,103 @@ public function waitUntilUserDropped(string $userName, string $domain = 'local') */ public function waitUntilGroupPresent(string $groupName): void { - printf("waiting until group %s present on all nodes\n", $groupName); + fprintf(STDERR, "waiting until group %s present on all nodes\n", $groupName); $this->waitUntilAllNodesMatchPredicate( "/settings/rbac/groups/$groupName", [$this, 'resourceIsPresent'], "Group $groupName is not present on all nodes", true, - MANAGEMENT_PORT + 'mgmt' ); } public function waitUntilGroupDropped(string $groupName): void { - printf("waiting until group %s dropped on all nodes\n", $groupName); + fprintf(STDERR, "waiting until group %s dropped on all nodes\n", $groupName); $this->waitUntilAllNodesMatchPredicate( "/settings/rbac/groups/$groupName", [$this, 'resourceIsDropped'], "Group $groupName is not dropped on all nodes", true, - MANAGEMENT_PORT + 'mgmt' ); } public function waitUntilBucketPresent(string $bucketName): void { - printf("waiting until bucket %s present on all nodes\n", $bucketName); + fprintf(STDERR, "waiting until bucket %s present on all nodes\n", $bucketName); $this->waitUntilAllNodesMatchPredicate( "/pools/default/buckets/$bucketName", [$this, 'resourceIsPresent'], "Bucket $bucketName is not present on all nodes", true, - MANAGEMENT_PORT + 'mgmt' ); } public function waitUntilBucketDropped(string $bucketName): void { - printf("waiting until bucket %s dropped on all nodes\n", $bucketName); + fprintf(STDERR, "waiting until bucket %s dropped on all nodes\n", $bucketName); $this->waitUntilAllNodesMatchPredicate( "/pools/default/buckets/$bucketName", [$this, 'resourceIsDropped'], "Bucket $bucketName is not dropped on all nodes", true, - MANAGEMENT_PORT + 'mgmt' ); } public function waitUntilDesignDocumentPresent(string $bucketName, string $designDocumentName): void { - printf("waiting until design document %s present on all nodes\n", $designDocumentName); + fprintf(STDERR, "waiting until design document %s present on all nodes\n", $designDocumentName); $this->waitUntilAllNodesMatchPredicate( "/{$bucketName}/_design/$designDocumentName", [$this, 'resourceIsPresent'], "Design document $designDocumentName on bucket $bucketName is not present on all nodes", true, - VIEWS_PORT + 'views' ); } public function waitUntilDesignDocumentDropped(string $bucketName, string $designDocumentName): void { - printf("waiting until design document %s dropped on all nodes\n", $designDocumentName); + fprintf(STDERR, "waiting until design document %s dropped on all nodes\n", $designDocumentName); $this->waitUntilAllNodesMatchPredicate( "/$bucketName/_design/$designDocumentName", [$this, 'resourceIsDropped'], "Design document $designDocumentName on bucket $bucketName is not dropped on all nodes", true, - VIEWS_PORT + 'views' ); } public function waitUntilViewPresent(string $bucketName, string $designDocumentName, string $viewName): void { - printf("waiting until view %s present on all nodes\n", $viewName); + fprintf(STDERR, "waiting until view %s present on all nodes\n", $viewName); $this->waitUntilAllNodesMatchPredicate( "/$bucketName/_design/$designDocumentName/_view/$viewName", [$this, 'resourceIsPresent'], "View $viewName on design document $designDocumentName on bucket $bucketName is not present on all nodes", true, - VIEWS_PORT + 'views' ); } public function waitUntilViewDropped(string $bucketName, string $designDocumentName, string $viewName): void { - printf("waiting until view %s dropped on all nodes\n", $viewName); + fprintf(STDERR, "waiting until view %s dropped on all nodes\n", $viewName); $this->waitUntilAllNodesMatchPredicate( "/$bucketName/_design/$designDocumentName/_view/$viewName", [$this, 'resourceIsDropped'], "View $viewName on design document $designDocumentName on bucket $bucketName is not dropped on all nodes", true, - VIEWS_PORT + 'views' ); } public function waitUntilScopePresent(string $bucketName, string $scopeName): void { - printf("waiting until scope %s present on all nodes\n", $scopeName); + fprintf(STDERR, "waiting until scope %s present on all nodes\n", $scopeName); $scopePresent = function ($response) use ($scopeName) { foreach ($response->scopes as $scope) { if ($scope->name == $scopeName) { @@ -174,13 +179,13 @@ public function waitUntilScopePresent(string $bucketName, string $scopeName): vo $scopePresent, "Scope $scopeName on bucket $bucketName is not present on all nodes", false, - MANAGEMENT_PORT + 'mgmt' ); } public function waitUntilScopeDropped(string $bucketName, string $scopeName): void { - printf("waiting until scope %s dropped on all nodes\n", $scopeName); + fprintf(STDERR, "waiting until scope %s dropped on all nodes\n", $scopeName); $scopeDropped = function ($response) use ($scopeName) { foreach ($response->scopes as $scope) { if ($scope->name == $scopeName) { @@ -195,13 +200,13 @@ public function waitUntilScopeDropped(string $bucketName, string $scopeName): vo $scopeDropped, "Scope $scopeName on bucket $bucketName is not dropped on all nodes", false, - MANAGEMENT_PORT + 'mgmt' ); } public function waitUntilCollectionPresent(string $bucketName, string $scopeName, string $collectionName): void { - printf("waiting until collection %s present on all nodes\n", $collectionName); + fprintf(STDERR, "waiting until collection %s present on all nodes\n", $collectionName); $collectionPresent = function ($response) use ($scopeName, $collectionName) { foreach ($response->scopes as $scope) { if ($scope->name == $scopeName) { @@ -220,13 +225,13 @@ public function waitUntilCollectionPresent(string $bucketName, string $scopeName $collectionPresent, "Collection $collectionName on scope $scopeName on bucket $bucketName is not present on all nodes", false, - MANAGEMENT_PORT + 'mgmt' ); } public function waitUntilCollectionDropped(string $bucketName, string $scopeName, string $collectionName): void { - printf("waiting until collection %s dropped on all nodes\n", $collectionName); + fprintf(STDERR, "waiting until collection %s dropped on all nodes\n", $collectionName); $collectionDropped = function ($response) use ($scopeName, $collectionName) { foreach ($response->scopes as $scope) { if ($scope->name == $scopeName) { @@ -245,54 +250,55 @@ public function waitUntilCollectionDropped(string $bucketName, string $scopeName $collectionDropped, "Collection $collectionName on scope $scopeName on bucket $bucketName is not dropped on all nodes", false, - MANAGEMENT_PORT + 'mgmt' ); } public function waitUntilBucketUpdated(string $bucketName, callable $predicate, string $errorMsg = null): void { - printf("waiting until bucket %s has been updated\n", $bucketName); + fprintf(STDERR, "waiting until bucket %s has been updated\n", $bucketName); $this->waitUntilAllNodesMatchPredicate( "/pools/default/buckets/$bucketName", $predicate, $errorMsg ?? "Bucket $bucketName has not been updated on all nodes", false, - MANAGEMENT_PORT + 'mgmt' ); } public function waitUntilCollectionUpdated(string $bucketName, string $scopeName, string $collectionName, callable $predicate, string $errorMsg = null): void { - printf("waiting until collection %s on scope %s on bucket %s has updated\n", $collectionName, $scopeName, $bucketName); + fprintf(STDERR, "waiting until collection %s on scope %s on bucket %s has updated\n", $collectionName, $scopeName, $bucketName); $this->waitUntilAllNodesMatchPredicate( "/pools/default/buckets/$bucketName/scopes", $predicate, $errorMsg ?? "Collection $collectionName has not been updated on all nodes", false, - MANAGEMENT_PORT + 'mgmt' ); } /** * @throws Exception */ - public function waitUntilAllNodesMatchPredicate(string $path, callable $predicate, string $errorMsg, bool $onlyStatusCode, int $port): void + public function waitUntilAllNodesMatchPredicate(string $path, callable $predicate, string $errorMsg, bool $onlyStatusCode, string $service, array $request = []): void { try { - $deadline = $this->currentTimeMillis() + 2000; + $deadline = $this->currentTimeMillis() + 200_000; while ($this->currentTimeMillis() < $deadline) { $predicateMatched = $this->allNodesMatchPredicate( $path, $predicate, $onlyStatusCode, - $port + $service, + $request ); if ($predicateMatched) { return; } - usleep(10_000); + usleep(100_000); } throw new Exception("Timed out waiting for nodes to match predicate"); } catch (Exception $e) { @@ -321,7 +327,7 @@ public function waitForConfig(bool $isMock): void } usleep(10_000); } catch (Exception $e) { - printf("Ignoring error waiting for config: %s", $e->getMessage()); + fprintf(STDERR, "Ignoring error waiting for config: %s", $e->getMessage()); } if (($this->currentTimeMillis() - $start) > 2000) { throw new Exception("Timeout waiting for config"); @@ -332,11 +338,70 @@ public function waitForConfig(bool $isMock): void /** * @throws Exception */ - private function allNodesMatchPredicate(string $path, callable $predicate, bool $onlyStatusCode, int $port): bool + public function waitUntilQueryIndexReady(string $bucketName, string $indexName, bool $isPrimary): void { - foreach ($this->nodes as $key => $value) { - $url = "http://" . $key . ":" . $port . $path; - $response = $this->runHttpRequest($url, $onlyStatusCode); + fprintf( + STDERR, + "waiting until query index \"%s\" (is_primary=%s) of bucket \"%s\" present on all nodes\n", + $indexName, + $isPrimary ? "true" : "false", + $bucketName + ); + $indexPresent = function ($response) use ($bucketName, $indexName, $isPrimary) { + foreach ($response->results as $result) { + $index = $result->indexes; + + if ( + $index->state == "online" && + $index->keyspace_id == $bucketName && + $index->name == $indexName && + (isset($index->is_primary) && $index->is_primary) == $isPrimary + ) { + return true; + } + } + return false; + }; + + + $this->waitUntilAllNodesMatchPredicate( + "/query/service", + $indexPresent, + sprintf("index %s (primary=%s) is not present on all nodes\n", $indexName, $isPrimary ? "true" : "false"), + false, + "n1ql", + [ + 'method' => 'POST', + 'header' => ['Content-type: application/json'], + 'content' => json_encode( + [ + 'statement' => 'SELECT * FROM system:indexes' + ] + ) + ] + ); + } + + /** + * @throws Exception + */ + private function allNodesMatchPredicate(string $path, callable $predicate, bool $onlyStatusCode, string $service, array $request): bool + { + foreach ($this->nodes as $hostname => $services) { + if ($service != "mgmt" && $service != "views") { + $found = false; + foreach ($services as $name) { + if ($service == $name) { + $found = true; + break; + } + } + if (!$found) { + continue; + } + } + $url = "http://" . $hostname . ":" . PORTS[$service] . $path; + $response = $this->runHttpRequest($url, $request, $onlyStatusCode); $predicateMatched = $predicate($response); if (!$predicateMatched) { return false; @@ -348,13 +413,18 @@ private function allNodesMatchPredicate(string $path, callable $predicate, bool /** * @throws Exception */ - private function runHttpRequest(string $url, bool $onlyStatusCode) + private function runHttpRequest(string $url, array $request, bool $onlyStatusCode) { + $headers = $request['header'] ?? []; + unset($request['header']); $opts = array('http' => - array( + array_merge( + [ 'method' => 'GET', - 'header' => $this->basicAuthString, + 'header' => array_merge([$this->basicAuthString], $headers), 'ignore_errors' => true, + ], + $request ) ); $context = stream_context_create($opts); @@ -363,7 +433,7 @@ private function runHttpRequest(string $url, bool $onlyStatusCode) if ($onlyStatusCode) { return $statusCode; } elseif ($statusCode != 200) { - throw new Exception("Non 200 status code from response"); + throw new Exception(sprintf("Non 200 status code from response (%d).\n%s", $statusCode, $response)); } return json_decode($response); } diff --git a/tests/Helpers/CouchbaseTestCase.php b/tests/Helpers/CouchbaseTestCase.php index aad05634..bcdbc70f 100644 --- a/tests/Helpers/CouchbaseTestCase.php +++ b/tests/Helpers/CouchbaseTestCase.php @@ -43,9 +43,10 @@ use Couchbase\Cluster; use Couchbase\ClusterInterface; use Couchbase\ClusterOptions; - use Couchbase\CollectionInterface; use Couchbase\WanDevelopmentProfile; +use Couchbase\Exception\CouchbaseException; + use Exception; use PHPUnit\Framework\TestCase; @@ -109,6 +110,9 @@ public function openBucket(string $name = null): BucketInterface public function defaultCollection(string $bucketName = null): CollectionInterface { + if ($bucketName == null) { + $bucketName = self::env()->bucketName(); + } return $this->openBucket($bucketName)->defaultCollection(); } @@ -211,6 +215,9 @@ public function retryFor(int $failAfterSecs, int $sleepMillis, callable $fn, $me while (time() <= $deadline) { try { return $fn(); + } catch (CouchbaseException $ex) { + fprintf(STDERR, "%s(%s) returned exception, will retry: %s\n%s\n", $caller, $message, $ex->getMessage(), var_export($ex->getContext(), true)); + $endException = $ex; } catch (Exception $ex) { fprintf(STDERR, "%s(%s) returned exception, will retry: %s\n", $caller, $message, $ex->getMessage()); $endException = $ex; @@ -290,7 +297,11 @@ protected function assertError($type, $code, $ex) protected function assertErrorType($type, $ex) { - $this->assertInstanceOf($type, $ex); + $this->assertInstanceOf( + $type, + $ex, + sprintf("Exception: %s, Message: %s", get_class($ex), $ex->getMessage()) + ); } protected function assertErrorMessage($msg, $ex) diff --git a/tests/KeyValueGetReplicaTest.php b/tests/KeyValueGetReplicaTest.php index b51e0e30..ed885276 100644 --- a/tests/KeyValueGetReplicaTest.php +++ b/tests/KeyValueGetReplicaTest.php @@ -50,9 +50,7 @@ public function testGetAllReplicasReturnCorrectValue() $res = $collection->upsert($id, ["answer" => 42], $opts); $cas = $res->cas(); $this->assertNotNull($cas); - if (self::env()->useCaves()) { - sleep(1); - } + sleep(1); $results = $collection->getAllReplicas($id); $this->assertGreaterThanOrEqual(1, count($results)); $seenActiveVersion = false; diff --git a/tests/KeyValueLookupInTest.php b/tests/KeyValueLookupInTest.php index 5e0478dc..60182759 100644 --- a/tests/KeyValueLookupInTest.php +++ b/tests/KeyValueLookupInTest.php @@ -25,6 +25,7 @@ use Couchbase\LookupGetSpec; use Couchbase\LookupInOptions; use Couchbase\UpsertOptions; +use Couchbase\DurabilityLevel; use Couchbase\LookupInAnyReplicaOptions; include_once __DIR__ . "/Helpers/CouchbaseTestCase.php"; @@ -124,7 +125,9 @@ public function testSubdocumentLookupAnyReplicaCanFetchExpiry() $id = $this->uniqueId("foo"); $collection = $this->defaultCollection(); - $res = $collection->upsert($id, ["foo" => "bar"]); + $options = UpsertOptions::build() + ->durabilityLevel(DurabilityLevel::MAJORITY_AND_PERSIST_TO_ACTIVE); + $res = $collection->upsert($id, ["foo" => "bar"], $options); $cas = $res->cas(); $res = $collection->lookupInAnyReplica( @@ -140,7 +143,10 @@ public function testSubdocumentLookupAnyReplicaCanFetchExpiry() $this->assertNull($res->expiryTime()); $birthday = DateTime::createFromFormat(DateTimeInterface::ISO8601, "2027-04-07T00:00:00UTC"); - $collection->upsert($id, ["foo" => "bar"], UpsertOptions::build()->expiry($birthday)); + $options = UpsertOptions::build() + ->expiry($birthday) + ->durabilityLevel(DurabilityLevel::MAJORITY_AND_PERSIST_TO_ACTIVE); + $collection->upsert($id, ["foo" => "bar"], $options); $deadline = time() + 5; /* 5 seconds from now */ @@ -169,7 +175,9 @@ public function testSubdocumentLookupAllReplicas() $id = $this->uniqueId(); $collection = $this->defaultCollection(); - $res = $collection->upsert($id, ["answer" => 42]); + $options = UpsertOptions::build() + ->durabilityLevel(DurabilityLevel::MAJORITY_AND_PERSIST_TO_ACTIVE); + $res = $collection->upsert($id, ["answer" => 42], $options); $cas = $res->cas(); $this->assertNotNull($cas); $results = $collection->lookupInAllReplicas( diff --git a/tests/QueryIndexManagerTest.php b/tests/QueryIndexManagerTest.php index f7c3f0c8..97b306d8 100644 --- a/tests/QueryIndexManagerTest.php +++ b/tests/QueryIndexManagerTest.php @@ -57,14 +57,28 @@ public function testQueryIndexesCrud() $this->bucketManager->createBucket($settings); $this->consistencyUtil()->waitUntilBucketPresent($bucketName); - $deadline = time() + 5; /* 5 seconds from now */ + $deadline = time() + 20; /* 20 seconds from now */ while (true) { try { - $manager->createPrimaryIndex($bucketName, CreateQueryPrimaryIndexOptions::build()->ignoreIfExists(true)); + try { + $manager->createPrimaryIndex( + $bucketName, + CreateQueryPrimaryIndexOptions::build() + ->timeout(200_000) + ->ignoreIfExists(true) + ); + } catch (\Couchbase\Exception\InternalServerException $ex) { + if (preg_match('/will be retried/', $ex->getMessage())) { + fprintf(STDERR, "Ignoring transient error during primary index creation for '%s': %s, %s\n", $bucketName, $ex->getMessage(), var_export($ex->getContext(), true)); + } else { + throw $ex; + } + } + $this->consistencyUtil()->waitUntilQueryIndexReady($bucketName, "#primary", true); break; } catch (CouchbaseException $ex) { - printf("Error during primary index creation for '%s': %s, %s", $bucketName, $ex->getMessage(), var_export($ex->getContext(), true)); + fprintf(STDERR, "Ignoring error during primary index creation for '%s': %s, %s\n", $bucketName, $ex->getMessage(), var_export($ex->getContext(), true)); if (time() > $deadline) { $this->fail("timed out waiting for create index to succeed"); } @@ -79,38 +93,46 @@ function () use ($manager, $bucketName) { IndexExistsException::class ); + $indexName = $this->uniqueId('testIndex'); $manager->createIndex( $bucketName, - "testIndex", + $indexName, ["field"], - CreateQueryIndexOptions::build()->ignoreIfExists(true) + CreateQueryIndexOptions::build() + ->timeout(200_000) + ->ignoreIfExists(true) ); + $this->consistencyUtil()->waitUntilQueryIndexReady($bucketName, $indexName, false); $this->wrapException( - function () use ($manager, $bucketName) { + function () use ($manager, $bucketName, $indexName) { $manager->createIndex( $bucketName, - "testIndex", + $indexName, ["field"], - CreateQueryIndexOptions::build()->ignoreIfExists(false) + CreateQueryIndexOptions::build() + ->timeout(200_000) + ->ignoreIfExists(false) ); }, IndexExistsException::class ); // We create this first to give it a chance to be created by the time we need it. + $deferredIndexName = $this->uniqueId('testIndexDeferred'); $manager->createIndex( $bucketName, - "testIndexDeferred", + $deferredIndexName, ["field"], CreateQueryIndexOptions::build() + ->timeout(200_000) ->ignoreIfExists(true) ->deferred(true) ); $manager->buildDeferredIndexes($bucketName); - $manager->watchIndexes($bucketName, ["testIndexDeferred"], 60_000); + $manager->watchIndexes($bucketName, [$deferredIndexName], 60_000); $indexes = $manager->getAllIndexes($bucketName); $this->assertGreaterThanOrEqual(3, count($indexes)); @@ -120,13 +142,13 @@ function () use ($manager, $bucketName) { * @var QueryIndex $entry */ foreach ($indexes as $entry) { - if ($entry->name() == "testIndex") { + if ($entry->name() == $indexName) { $index = $entry; } } $this->assertNotNull($index); - $this->assertEquals("testIndex", $index->name()); + $this->assertEquals($indexName, $index->name()); $this->assertFalse($index->isPrimary()); $this->assertEquals(QueryIndexType::GSI, $index->type()); $this->assertEquals("online", $index->state()); @@ -137,11 +159,11 @@ function () use ($manager, $bucketName) { $this->assertNull($index->condition()); $this->assertNull($index->partition()); - $manager->dropIndex($bucketName, "testIndex"); + $manager->dropIndex($bucketName, $indexName); $this->wrapException( - function () use ($manager, $bucketName) { - $manager->dropIndex($bucketName, "testIndex"); + function () use ($manager, $bucketName, $indexName) { + $manager->dropIndex($bucketName, $indexName); }, IndexNotFoundException::class ); @@ -166,10 +188,23 @@ public function testCollectionQueryIndexesCrud() while (true) { try { - $manager->createPrimaryIndex(CreateQueryPrimaryIndexOptions::build()->ignoreIfExists(true)); + try { + $manager->createPrimaryIndex( + CreateQueryPrimaryIndexOptions::build() + ->timeout(200_000) + ->ignoreIfExists(true) + ); + } catch (\Couchbase\Exception\InternalServerException $ex) { + if (preg_match('/will be retried/', $ex->getMessage())) { + fprintf(STDERR, "Ignoring transient error during primary index creation for '%s': %s, %s\n", $bucketName, $ex->getMessage(), var_export($ex->getContext(), true)); + } else { + throw $ex; + } + } + $this->consistencyUtil()->waitUntilQueryIndexReady($this->env()->bucketName(), "#primary", true); break; } catch (CouchbaseException $ex) { - printf("Error during primary index creation: %s, %s", $ex->getMessage(), var_export($ex->getContext(), true)); + fprintf(STDERR, "Ignoring error during primary index creation: %s, %s\n", $ex->getMessage(), var_export($ex->getContext(), true)); if (time() > $deadline) { $this->assertFalse("timed out waiting for create index to succeed"); } @@ -184,35 +219,43 @@ function () use ($manager) { IndexExistsException::class ); + $indexName = $this->uniqueId('testIndex'); $manager->createIndex( - "testIndex", + $indexName, ["field"], - CreateQueryIndexOptions::build()->ignoreIfExists(true) + CreateQueryIndexOptions::build() + ->timeout(200_000) + ->ignoreIfExists(true) ); + $this->consistencyUtil()->waitUntilQueryIndexReady($this->env()->bucketName(), $indexName, false); $this->wrapException( - function () use ($manager) { + function () use ($manager, $indexName) { $manager->createIndex( - "testIndex", + $indexName, ["field"], - CreateQueryIndexOptions::build()->ignoreIfExists(false) + CreateQueryIndexOptions::build() + ->timeout(200_000) + ->ignoreIfExists(false) ); }, IndexExistsException::class ); // We create this first to give it a chance to be created by the time we need it. + $deferredIndexName = $this->uniqueId('testIndexDeferred'); $manager->createIndex( - "testIndexDeferred", + $deferredIndexName, ["field"], CreateQueryIndexOptions::build() + ->timeout(200_000) ->ignoreIfExists(true) ->deferred(true) ); $manager->buildDeferredIndexes(); - $manager->watchIndexes(["testIndexDeferred"], 60_000); + $manager->watchIndexes([$deferredIndexName], 60_000); $indexes = $manager->getAllIndexes(); $this->assertGreaterThanOrEqual(3, count($indexes)); @@ -222,13 +265,13 @@ function () use ($manager) { * @var QueryIndex $entry */ foreach ($indexes as $entry) { - if ($entry->name() == "testIndex") { + if ($entry->name() == $indexName) { $index = $entry; } } $this->assertNotNull($index); - $this->assertEquals("testIndex", $index->name()); + $this->assertEquals($indexName, $index->name()); $this->assertFalse($index->isPrimary()); $this->assertEquals(QueryIndexType::GSI, $index->type()); $this->assertEquals("online", $index->state()); @@ -238,11 +281,11 @@ function () use ($manager) { $this->assertNull($index->condition()); $this->assertNull($index->partition()); - $manager->dropIndex("testIndex"); + $manager->dropIndex($indexName); $this->wrapException( - function () use ($manager) { - $manager->dropIndex("testIndex"); + function () use ($manager, $indexName) { + $manager->dropIndex($indexName); }, IndexNotFoundException::class ); diff --git a/tests/SearchIndexManagerTest.php b/tests/SearchIndexManagerTest.php index ef6952b8..56730433 100644 --- a/tests/SearchIndexManagerTest.php +++ b/tests/SearchIndexManagerTest.php @@ -6,6 +6,7 @@ use Couchbase\Management\ScopeSearchIndexManagerInterface; use Couchbase\Management\SearchIndex; use Couchbase\Management\SearchIndexManagerInterface; +use Couchbase\Management\AnalyzeDocumentOptions; include_once __DIR__ . "/Helpers/CouchbaseTestCase.php"; @@ -115,10 +116,14 @@ public function testAnalyzeDocument() $this->manager->upsertIndex($index); $tokens = $this->retryFor( - 5, - 1000, + 60, + 2000, function () { - return $this->manager->analyzeDocument($this->indexName, ["name" => "hello world"]); + return $this->manager->analyzeDocument( + $this->indexName, + ["name" => "hello world"], + AnalyzeDocumentOptions::build()->timeout(200_000) + ); } ); $this->assertNotEmpty($tokens); diff --git a/tests/SearchTest.php b/tests/SearchTest.php index 84eb7921..314388d4 100644 --- a/tests/SearchTest.php +++ b/tests/SearchTest.php @@ -31,6 +31,9 @@ use Couchbase\Exception\IndexNotFoundException; use Couchbase\GeoBoundingBoxSearchQuery; use Couchbase\GeoDistanceSearchQuery; +use Couchbase\Management\BucketSettings; +use Couchbase\Management\BucketType; +use Couchbase\Management\BucketManager; use Couchbase\Management\SearchIndex; use Couchbase\Management\SearchIndexManager; use Couchbase\MatchAllSearchQuery; @@ -68,6 +71,9 @@ class SearchTest extends Helpers\CouchbaseTestCase private ClusterInterface $cluster; private CollectionInterface $collection; private SearchIndexManager $indexManager; + private BucketManager $bucketManager; + private string $bucketName; + private string $indexName; /** * @return number of the documents in dataset @@ -86,9 +92,9 @@ public function loadDataset(): int public function createSearchIndex(int $datasetSize): void { - fprintf(STDERR, "Create 'beer-search' to index %d docs\n", $datasetSize); + fprintf(STDERR, "Create \"%s\" to index %d docs in bucket \"%s\"\n", $this->indexName, $datasetSize, $this->bucketName); $indexDump = json_decode(file_get_contents(__DIR__ . "/beer-search.json"), true); - $index = SearchIndex::build("beer-search", self::env()->bucketName()); + $index = SearchIndex::build($this->indexName, $this->bucketName); $index->setParams($indexDump["params"]); $this->indexManager->upsertIndex($index); @@ -97,8 +103,8 @@ public function createSearchIndex(int $datasetSize): void $start = time(); while (true) { try { - $indexedDocuments = $this->indexManager->getIndexedDocumentsCount("beer-search"); - fprintf(STDERR, "%ds, Indexing 'beer-search': %d docs\n", time() - $start, $indexedDocuments); + $indexedDocuments = $this->indexManager->getIndexedDocumentsCount($this->indexName); + fprintf(STDERR, "%ds, Indexing \"%s\": %d docs\n", time() - $start, $this->indexName, $indexedDocuments); if ($indexedDocuments >= $datasetSize) { // the indexer settled on the same number of the documents // since last check @@ -118,6 +124,11 @@ public function createSearchIndex(int $datasetSize): void $previousIndexed = $indexedDocuments; sleep(4); } catch (\Couchbase\Exception\IndexNotReadyException $ex) { + fprintf(STDERR, "%ds, Index not ready, retrying in 1 second\n", time() - $start); + sleep(1); + } catch (\Couchbase\Exception\IndexNotFoundException $ex) { + fprintf(STDERR, "%ds, Index not found, retrying in 1 second\n", time() - $start); + sleep(1); } } } @@ -126,13 +137,32 @@ public function setUp(): void { parent::setUp(); - $this->cluster = $this->connectCluster(); - $this->collection = $this->openBucket(self::env()->bucketName())->defaultCollection(); - if (self::env()->useCouchbase()) { + $this->bucketName = $this->uniqueId("beerdb"); + $this->indexName = $this->uniqueId("beer-search"); + + $this->cluster = $this->connectCluster(); + + $this->bucketManager = $this->cluster->buckets(); + + $settings = new BucketSettings($this->bucketName); + $settings->setBucketType(BucketType::COUCHBASE); + $this->bucketManager->createBucket($settings); + $this->consistencyUtil()->waitUntilBucketPresent($this->bucketName); + + while (true) { + sleep(1); + try { + $this->collection = $this->openBucket($this->bucketName)->defaultCollection(); + break; + } catch (BucketNotFoundException $ex) { + // do nothing + } + } + $this->indexManager = $this->cluster->searchIndexes(); try { - $this->indexManager->getIndex("beer-search"); + $this->indexManager->getIndex($this->indexName); } catch (IndexNotFoundException $ex) { $this->createSearchIndex($this->loadDataset()); } @@ -143,6 +173,21 @@ public function tearDown(): void { parent::tearDown(); + if (self::env()->useCouchbase()) { + try { + $this->bucketManager->dropBucket($this->bucketName); + $this->consistencyUtil()->waitUntilBucketDropped($this->bucketName); + } catch (BucketNotFoundException $ex) { + /* do nothing */ + } + + try { + $this->indexManager->dropIndex($this->indexName); + } catch (IndexNotFoundException $ex) { + /* do nothing */ + } + } + // close cluster here? } @@ -153,7 +198,7 @@ public function testSearchWithLimit() $query = new MatchPhraseSearchQuery("hop beer"); $options = SearchOptions::build()->limit(3); - $result = $this->cluster->searchQuery("beer-search", $query, $options); + $result = $this->cluster->searchQuery($this->indexName, $query, $options); $this->assertNotNull($result); $this->assertNotEmpty($result->rows()); @@ -162,7 +207,7 @@ public function testSearchWithLimit() foreach ($result->rows() as $hit) { $this->assertNotNull($hit['id']); - $this->assertStringStartsWith("beer-search", $hit['index']); + $this->assertStringStartsWith($this->indexName, $hit['index']); $this->assertGreaterThan(0, $hit['score']); } } @@ -175,7 +220,7 @@ public function testSearchQueryWithRequestApi() $options = SearchOptions::build()->limit(3); $request = SearchRequest::build($query); - $result = $this->cluster->search("beer-search", $request, $options); + $result = $this->cluster->search($this->indexName, $request, $options); $this->assertNotNull($result); $this->assertNotEmpty($result->rows()); @@ -184,7 +229,7 @@ public function testSearchQueryWithRequestApi() foreach ($result->rows() as $hit) { $this->assertNotNull($hit['id']); - $this->assertStringStartsWith("beer-search", $hit['index']); + $this->assertStringStartsWith($this->indexName, $hit['index']); $this->assertGreaterThan(0, $hit['score']); } } @@ -196,7 +241,7 @@ public function testSearchWithNoHits() $query = new MatchPhraseSearchQuery("doesnotexistintheindex"); $options = SearchOptions::build()->limit(3); - $result = $this->cluster->searchQuery("beer-search", $query, $options); + $result = $this->cluster->searchQuery($this->indexName, $query, $options); $this->assertNotNull($result); $this->assertEmpty($result->rows()); @@ -212,7 +257,7 @@ public function testSearchWithConsistency() $query = new MatchPhraseSearchQuery($id); $options = SearchOptions::build()->limit(3)->timeout(120_000); - $result = $this->cluster->searchQuery("beer-search", $query, $options); + $result = $this->cluster->searchQuery($this->indexName, $query, $options); $this->assertNotNull($result); $this->assertEmpty($result->rows()); @@ -222,14 +267,14 @@ public function testSearchWithConsistency() $mutationState = new MutationState(); $mutationState->add($result); - $options->consistentWith("beer-search", $mutationState); + $options->consistentWith($this->indexName, $mutationState); // Eventual consistency for consistent with... $result = $this->retryFor( 120, 1000, function () use ($query, $options) { - $result = $this->cluster->searchQuery("beer-search", $query, $options); + $result = $this->cluster->searchQuery($this->indexName, $query, $options); if (count($result->rows()) == 0) { throw new Exception("Excepted rows to not to be empty"); } @@ -252,7 +297,7 @@ public function testSearchWithFields() $query = new MatchPhraseSearchQuery("hop beer"); $options = SearchOptions::build()->limit(3)->fields([$nameField]); - $result = $this->cluster->searchQuery("beer-search", $query, $options); + $result = $this->cluster->searchQuery($this->indexName, $query, $options); $this->assertNotNull($result); $this->assertNotEmpty($result->rows()); @@ -261,7 +306,7 @@ public function testSearchWithFields() foreach ($result->rows() as $hit) { $this->assertNotNull($hit['id']); - $this->assertStringStartsWith("beer-search", $hit['index']); + $this->assertStringStartsWith($this->indexName, $hit['index']); $this->assertGreaterThan(0, $hit['score']); $this->assertNotEmpty($hit['fields']); $this->assertNotNull($hit['fields']['name']); @@ -277,9 +322,9 @@ public function testSearchWithSkip() $options_low = SearchOptions::build()->limit(3)->skip(10); $options_excess = SearchOptions::build()->limit(3)->skip(7000); - $result_none = $this->cluster->searchQuery("beer-search", $query, $options_none); - $result_low = $this->cluster->searchQuery("beer-search", $query, $options_low); - $result_excess = $this->cluster->searchQuery("beer-search", $query, $options_excess); + $result_none = $this->cluster->searchQuery($this->indexName, $query, $options_none); + $result_low = $this->cluster->searchQuery($this->indexName, $query, $options_low); + $result_excess = $this->cluster->searchQuery($this->indexName, $query, $options_excess); $this->assertNotNull($result_none); $this->assertNotNull($result_low); @@ -307,7 +352,7 @@ public function testSearchWithSort() ] ); - $result = $this->cluster->searchQuery("beer-search", $query, $options); + $result = $this->cluster->searchQuery($this->indexName, $query, $options); $this->assertNotNull($result); $this->assertNotEmpty($result->rows()); @@ -316,7 +361,7 @@ public function testSearchWithSort() foreach ($result->rows() as $hit) { $this->assertNotNull($hit['id']); - $this->assertStringStartsWith("beer-search", $hit['index']); + $this->assertStringStartsWith($this->indexName, $hit['index']); $this->assertGreaterThan(0, $hit['score']); } } @@ -330,7 +375,7 @@ public function testSearchWithRanges() $query = (new NumericRangeSearchQuery())->field("abv")->min(2.0)->max(3.2); $options = SearchOptions::build()->fields(["abv"]); - $result = $this->cluster->searchQuery("beer-search", $query, $options); + $result = $this->cluster->searchQuery($this->indexName, $query, $options); $this->assertNotNull($result); $this->assertNotEmpty($result->rows()); @@ -338,7 +383,7 @@ public function testSearchWithRanges() foreach ($result->rows() as $hit) { $this->assertNotNull($hit['id']); - $this->assertStringStartsWith("beer-search", $hit['index']); + $this->assertStringStartsWith($this->indexName, $hit['index']); $this->assertGreaterThan(0, $hit['score']); $this->assertNotEmpty($hit['fields']); $this->assertNotNull($hit['fields']['abv']); @@ -355,7 +400,7 @@ public function testSearchWithRanges() ] ); $options = SearchOptions::build()->fields(["updated", "type"]); - $result = $this->cluster->searchQuery("beer-search", $query, $options); + $result = $this->cluster->searchQuery($this->indexName, $query, $options); $this->assertNotNull($result); $this->assertNotEmpty($result->rows()); @@ -364,7 +409,7 @@ public function testSearchWithRanges() $endDate = new DateTime('2010-12-01 20:00:00'); foreach ($result->rows() as $hit) { $this->assertNotNull($hit['id']); - $this->assertStringStartsWith("beer-search", $hit['index']); + $this->assertStringStartsWith($this->indexName, $hit['index']); $this->assertGreaterThan(0, $hit['score']); $this->assertNotEmpty($hit['fields']); $this->assertNotNull($hit['fields']['updated']); @@ -392,6 +437,7 @@ public function testSearchWithRanges() } } + public function testCompoundSearchQueries() { $this->skipIfCaves(); @@ -401,24 +447,25 @@ public function testCompoundSearchQueries() $disjunctionQuery = new DisjunctionSearchQuery([$nameQuery, $descriptionQuery]); $options = SearchOptions::build()->fields(["type", "name", "description"]); - $result = $this->cluster->searchQuery("beer-search", $disjunctionQuery, $options); + $result = $this->cluster->searchQuery($this->indexName, $disjunctionQuery, $options); $this->assertGreaterThanOrEqual(10, $result->metaData()->totalHits()); $this->assertNotEmpty($result->rows()); - $this->assertMatchesRegularExpression('/green/i', $result->rows()[0]['fields']['name']); - $this->assertDoesNotMatchRegularExpression('/fuggles/i', $result->rows()[0]['fields']['name']); - $this->assertMatchesRegularExpression('/fuggles/i', $result->rows()[0]['fields']['description']); - $this->assertDoesNotMatchRegularExpression('/green/i', $result->rows()[0]['fields']['description']); + // disjunction: either name matches /green/ or description matches /fuggles/ + $this->assertTrue( + preg_match('/green/i', $result->rows()[0]['fields']['name']) || + preg_match('/fuggles/i', $result->rows()[0]['fields']['description']) + ); $disjunctionQuery->min(2); $options = SearchOptions::build()->fields(["type", "name", "description"]); - $result = $this->cluster->searchQuery("beer-search", $disjunctionQuery, $options); + $result = $this->cluster->searchQuery($this->indexName, $disjunctionQuery, $options); $this->assertNotEmpty($result->rows()); $this->assertLessThan(10, $result->metaData()->totalHits()); $disjunctionResult = $result; $conjunctionQuery = new ConjunctionSearchQuery([$nameQuery, $descriptionQuery]); $options = SearchOptions::build()->fields(["type", "name", "description"]); - $result = $this->cluster->searchQuery("beer-search", $conjunctionQuery, $options); + $result = $this->cluster->searchQuery($this->indexName, $conjunctionQuery, $options); $this->assertNotEmpty($result->rows()); $this->assertSameSize($disjunctionResult->rows(), $result->rows()); $this->assertEquals( @@ -440,7 +487,7 @@ public function testSearchWithFragments() ->limit(3) ->highlight(SearchHighlightMode::HTML, ["name"]); - $result = $this->cluster->searchQuery("beer-search", $query, $options); + $result = $this->cluster->searchQuery($this->indexName, $query, $options); $this->assertNotEmpty($result->rows()); foreach ($result->rows() as $hit) { @@ -470,7 +517,7 @@ public function testSearchWithFacets() ] ); - $result = $this->cluster->searchQuery("beer-search", $query, $options); + $result = $this->cluster->searchQuery($this->indexName, $query, $options); $this->assertNotEmpty($result->rows()); $this->assertNotEmpty($result->facets()); diff --git a/tests/ViewTest.php b/tests/ViewTest.php index fb7f600b..ade570bd 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -5,25 +5,62 @@ use Couchbase\Extension; use Couchbase\ViewConsistency; use Couchbase\ViewOptions; +use Couchbase\UpsertOptions; +use Couchbase\DurabilityLevel; +use Couchbase\Management\BucketManager; +use Couchbase\Management\BucketType; +use Couchbase\Management\BucketSettings; include_once __DIR__ . "/Helpers/CouchbaseTestCase.php"; class ViewTest extends Helpers\CouchbaseTestCase { private ClusterInterface $cluster; + private BucketManager $bucketManager; + private string $bucketName; public function setUp(): void { parent::setUp(); $this->skipIfProtostellar(); + $this->skipIfCaves(); $this->cluster = $this->connectCluster(); + + $this->bucketName = $this->uniqueId("viewtest"); + + $this->bucketManager = $this->cluster->buckets(); + $settings = new BucketSettings($this->bucketName); + $settings->setBucketType(BucketType::COUCHBASE); + $this->bucketManager->createBucket($settings); + $this->consistencyUtil()->waitUntilBucketPresent($this->bucketName); + + while (true) { + sleep(1); + try { + $this->collection = $this->openBucket($this->bucketName)->defaultCollection(); + break; + } catch (BucketNotFoundException $ex) { + // do nothing + } + } + } + + public function tearDown(): void + { + try { + $this->bucketManager->dropBucket($this->bucketName); + $this->consistencyUtil()->waitUntilBucketDropped($this->bucketName); + } catch (BucketNotFoundException $ex) { + /* do nothing */ + } } public function testConsistency() { $this->skipIfCaves(); + $ddocName = $this->uniqueId(); $view = [ 'name' => 'test', @@ -38,14 +75,16 @@ public function testConsistency() ], ]; - $bucketName = $this->env()->bucketName(); - Extension\viewIndexUpsert($this->cluster->core(), $bucketName, $ddoc, DesignDocumentNamespace::PRODUCTION, []); - $this->consistencyUtil()->waitUntilViewPresent($bucketName, $ddocName, 'test'); + Extension\viewIndexUpsert($this->cluster->core(), $this->bucketName, $ddoc, DesignDocumentNamespace::PRODUCTION, []); + $this->consistencyUtil()->waitUntilViewPresent($this->bucketName, $ddocName, 'test'); sleep(1); // give design document a second to settle $key = $this->uniqueId($ddocName); - $bucket = $this->cluster->bucket($bucketName); - $bucket->defaultCollection()->upsert($key, ['foo' => 42]); + $bucket = $this->cluster->bucket($this->bucketName); + + $options = UpsertOptions::build() + ->durabilityLevel(DurabilityLevel::MAJORITY_AND_PERSIST_TO_ACTIVE); + $bucket->defaultCollection()->upsert($key, ['foo' => 42], $options); $res = $bucket->viewQuery($ddocName, 'test'); $this->assertEmpty($res->rows()); @@ -53,7 +92,7 @@ public function testConsistency() $options = ViewOptions::build() ->scanConsistency(ViewConsistency::REQUEST_PLUS) ->reduce(false) - ->timeout(120_000); + ->timeout(200_000); $res = $bucket->viewQuery($ddocName, 'test', $options); $this->assertCount(1, $res->rows()); $this->assertEquals($key, $res->rows()[0]->id()); @@ -79,33 +118,39 @@ public function testGrouping() ], ]; - $bucketName = $this->env()->bucketName(); - Extension\viewIndexUpsert($this->cluster->core(), $bucketName, $ddoc, DesignDocumentNamespace::PRODUCTION, []); - $this->consistencyUtil()->waitUntilViewPresent($bucketName, $ddocName, 'test'); + Extension\viewIndexUpsert($this->cluster->core(), $this->bucketName, $ddoc, DesignDocumentNamespace::PRODUCTION, []); + $this->consistencyUtil()->waitUntilViewPresent($this->bucketName, $ddocName, 'test'); sleep(1); // give design document a second to settle - $bucket = $this->cluster->bucket($bucketName); + $bucket = $this->cluster->bucket($this->bucketName); $collection = $bucket->defaultCollection(); + $options = UpsertOptions::build() + ->durabilityLevel(DurabilityLevel::MAJORITY_AND_PERSIST_TO_ACTIVE); + $collection->upsert( $this->uniqueId($ddocName), - ['ddoc' => $ddocName, 'country' => 'USA', 'city' => 'New York', 'name' => 'John Doe'] + ['ddoc' => $ddocName, 'country' => 'USA', 'city' => 'New York', 'name' => 'John Doe'], + $options ); $collection->upsert( $this->uniqueId($ddocName), - ['ddoc' => $ddocName, 'country' => 'USA', 'city' => 'New York', 'name' => 'Jane Doe'] + ['ddoc' => $ddocName, 'country' => 'USA', 'city' => 'New York', 'name' => 'Jane Doe'], + $options ); $collection->upsert( $this->uniqueId($ddocName), - ['ddoc' => $ddocName, 'country' => 'USA', 'city' => 'Miami', 'name' => 'Bill Brown'] + ['ddoc' => $ddocName, 'country' => 'USA', 'city' => 'Miami', 'name' => 'Bill Brown'], + $options ); $collection->upsert( $this->uniqueId($ddocName), - ['ddoc' => $ddocName, 'country' => 'France', 'city' => 'Paris', 'name' => 'Jean Bon'] + ['ddoc' => $ddocName, 'country' => 'France', 'city' => 'Paris', 'name' => 'Jean Bon'], + $options ); sleep(1); // give docs time to propagate - $options = ViewOptions::build()->scanConsistency(ViewConsistency::REQUEST_PLUS)->timeout(120_000); + $options = ViewOptions::build()->scanConsistency(ViewConsistency::REQUEST_PLUS)->timeout(200_000); $res = $bucket->viewQuery($ddocName, 'test', $options); $this->assertCount(1, $res->rows()); $this->assertEquals(4, $res->rows()[0]->value()); diff --git a/tests/beer-data.json b/tests/beer-data.json index 45e7f3cc..04340948 100644 --- a/tests/beer-data.json +++ b/tests/beer-data.json @@ -1,41 +1,41 @@ { - "anheuser_busch-green_valley_wild_hop_lager": {"name":"Green Valley Wild Hop Lager","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"anheuser_busch","updated":"2010-07-22 20:00:20","description":"","style":"American-Style Lager","category":"North American Lager"}, + "anheuser_busch-green_valley_wild_hop_lager": {"name":"Green Valley Wild Hop Lager","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"anheuser_busch","updated":"2010-07-22 20:00:20","description":"Green Valley Brewing Organic Wild Hop Lager is a American Lager style beer brewed by Anheuser-Busch in Saint Louis, MO.","style":"American-Style Lager","category":"North American Lager"}, "aspen_brewing_company-conundrum_red_ale": {"name":"Conundrum Red Ale","abv":7.0,"ibu":65.0,"srm":13.0,"upc":0,"type":"beer","brewery_id":"aspen_brewing_company","updated":"2010-11-08 08:45:46","description":"Have you ever heard of an ESB? In most cases, it stands for Extra Special Bitter, and man is this red extra special. Conundrum walks the line between a malt-centric sweet ale and a hop-centric bitter ale so well, it","style":"American-Style Amber/Red Ale","category":"North American Ale"}, - "atwater_block_brewing-alt": {"name":"Alt","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"atwater_block_brewing","updated":"2010-07-22 20:00:20","description":"","style":"American-Style Brown Ale","category":"North American Ale"}, + "atwater_block_brewing-alt": {"name":"Alt","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"atwater_block_brewing","updated":"2010-07-22 20:00:20","description":"Altbier, brewed by Atwater Block Brewing, is a German-style beer known for its smooth maltiness and balanced bitterness. Originating from Düsseldorf, Altbier (meaning \"old beer\") is a top-fermented ale that undergoes a cool fermentation process, similar to lagers, resulting in a crisp, clean finish.","style":"American-Style Brown Ale","category":"North American Ale"}, "big_sky_brewing-powder_hound_winter_ale": {"name":"Powder Hound Winter Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"big_sky_brewing","updated":"2010-07-22 20:00:20","description":"Two words describe Montana winters... Cold, dark, and snowy. And we wouldn't have it any other way, because it's the only time of year for Powder Hound, our Northern Rockies Winter Ale. Rich malt taste and an avalanche of hops will make you want to say these three words- \"I'll have another Powder Hound!\" \r\n\r\nSince Powder Hound is our own creation, it does not fit neatly into a style. Some folks would call it an \"Old Ale\" and others might call it a \"Winter Warmer\". Regardless of what you call it, Powder Hound satisfies, with the fine, hand-selected British hops fully complementing the smooth malt notes. After a day on the snow, enjoy a Powder Hound Winter Ale. Available November through March.","style":"Old Ale","category":"British Ale"}, "boston_beer_company-samuel_adams_hallertau_imperial_pilsner": {"name":"Samuel Adams Hallertau Imperial Pilsner","abv":8.8,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"boston_beer_company","updated":"2010-07-22 20:00:20","description":"An amazing treat for hops lovers.\r\n\r\nSamuel Adams Hallertau Imperial Pilsner is a celebration of the extraordinary Hallertau Mittelfrueh hop variety. This rare Noble Bavarian hop, considered to be one of the best in the world, is prized for its quality and aromatic characteristics.\r\n\r\nThe beer, which is a deep golden color with a rich, creamy head, gives off an intense and complex Noble hop aroma unlike any other brew. With the first sip, beer enthusiasts will experience an explosion of hop flavor. The intensity of deep citrus, spicy Noble hop flavor is balanced with the slight sweetness from the malt. Due to the quality of the hops, this beer remains balanced and smoothly drinkable from beginning to end. The lingering \"hop signature\" is an amazing treat for hops lovers."}, - "brewpub_on_the_green": {"name":"Brewpub-on-the-Green","city":"Fremont","state":"California","code":"","country":"United States","phone":"","website":"","type":"brewery","updated":"2010-07-22 20:00:20","description":"","address":[],"geo":{"accuracy":"APPROXIMATE","lat":37.5483,"lon":-121.989}}, - "bulldog_brewing-aztec_amaranth_ale": {"name":"Aztec Amaranth Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"bulldog_brewing","updated":"2010-07-22 20:00:20","description":""}, + "brewpub_on_the_green": {"name":"Brewpub-on-the-Green","city":"Fremont","state":"California","code":"","country":"United States","phone":"","website":"","type":"brewery","updated":"2010-07-22 20:00:20","description":"Brewpub-on-the-Green was a popular and historic brewpub located in Detroit, Michigan. Situated near Campus Martius Park, it was once a vibrant part of the city’s downtown revitalization efforts. As a brewpub, it offered an array of craft beers brewed in-house, alongside a cozy, casual dining experience.","address":[],"geo":{"accuracy":"APPROXIMATE","lat":37.5483,"lon":-121.989}}, + "bulldog_brewing-aztec_amaranth_ale": {"name":"Aztec Amaranth Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"bulldog_brewing","updated":"2010-07-22 20:00:20","description":"Aztec Amaranth Ale is an adventurous craft beer inspired by ancient Aztec ingredients and brewing traditions. This unique ale often incorporates amaranth, an ancient grain that was a staple in the Aztec diet, giving the beer a distinctive, earthy, and slightly nutty flavor profile."}, "coopers_brewery-coopers_premium_light": {"name":"Coopers Premium Light","abv":2.9,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"coopers_brewery","updated":"2010-07-22 20:00:20","description":"Coopers Premium Light is brewed using malted barley and no sugar. Coopers has used traditional lager brewing techniques to produce a full-flavoured light beer. The light has a fresh aroma with clean floral hop notes, excellent head and colour presentation.","style":"American-Style Light Lager","category":"North American Lager"}, "deschutes_brewery-green_lakes_organic_ale": {"name":"Green Lakes Organic Ale","abv":5.2,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"deschutes_brewery","updated":"2010-07-22 20:00:20","description":"Discover Deschutes Brewery’s intriguing amber ale, our Ode to Mother Earth. Experience its mellow malt profile intertwined with subtly surprising hop flavors. Green Lakes Organic Ale is brewed with five types of 100% organic malted barley and balanced with Liberty and Salmon-Safe Sterling hops. Easy to drink. Easy on the Environment. Downright Delicious. Who knew celebrating Mother Earth could taste so good.\r\n\r\nAfter working with Oregon Tilth for nearly six months, Deschutes Brewery received organic certification for its 50 barrel brew house and can now brew tasty organic ales for year-round enjoyment.\r\n\r\nFish need cool clean water. So do you. That’s why we sourced Salmon-Safe certified Sterling hops for our first organic beer. The way these flavorful, rich hops are grown makes sure that streams are shaded and there is not runoff to nearby waterways. That way the rivers stay cool and clean for migrating salmon. Not only is our Green Lakes beer organic, it helps protect our rivers as well.","style":"American-Style Amber/Red Ale","category":"North American Ale"}, "deschutes_brewery-hop_henge_imperial_ipa": {"name":"Hop Henge Imperial IPA","abv":8.75,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"deschutes_brewery","updated":"2010-07-22 20:00:20","description":"HOP HENGE IMPERIAL IPA – The Ultimate in Hop Innovation \r\n\r\nHop Henge Imperial IPA returns to the Bond Street Series line-up this April in extreme fashion. Staying true to the experimental nature of the series and the “never settle” philosophy of Deschutes, our brewers went back to the drawing board to create an amplified version of last year’s monument to hops.\r\n\r\nHead Brewer Brett Porter says, “This is a truly exciting and groundbreaking beer. We reformulated everything about the hop recipe to give Hop Henge an extraordinary aroma and flavor similar to a fresh hop beer.” In addition to the Cascade and Centennial hops, Deschutes Brewery is experimenting with a hop variety so new that it has yet to be named.\r\n\r\nThe team here at Bend Public House recommends Hop Henge as the perfect accompaniment for a variety of spicy foods, so be sure to have a bottle handy next time you make a batch of hot wings and go for the five alarm award. The high-octane hoppiness is a wildly refreshing antidote to a wide array of hot foods.\r\n\r\nLimited Availability April through June.\r\n\r\nDon’t miss this amazing hop experiment that is sure to leave your taste buds begging for more.\r\n\r\n-http://www.deschutesbrewery.com/Brews/Bond+Street+Series/default.aspx\r\n\r\nAvailable April through June.\r\n\r\nHop Henge I.P.A. was first brewed in 2006 as an agressive West Coast style I.P.A. In 2007, Hop Henge is going Imperial! Several pounds of Centennial, Cascade and Northern Brewer hops are in each barrel with a heavy dry-hop presence to top it off. A blend of crystal, pale and caraston malts creates an overall biscuity characteristic that is dense and muscular, building the alcohol base to support the monstrous hop profile.\r\n\r\nLike the rest of the Bond Street Series, Hop Henge highlights the creativity and curiosity of our brewers. Bolder than its English ancestors, with huge hops and a bitter finish, this IPA is no wallflower.\r\n\r\nAlcohol By Volume: 8.1%\r\n\t\r\n\r\nIBU 95\r\n\r\nWhen one of our brewers suggested we name our new IPA Hop Henge, he also came up with the idea of actually recreating Stonehenge, only with hop bales. We were up for the challenge and even though the weather did not want to cooperate, we pulled it off and threw a party afterwards.\r\n\t\r\n\r\n\r\nphoto: chris mather\r\nRatings, Awards & Notables\r\n\r\nWorld's Best Strong Pale Ale (Imperial IPA)\r\n2007 World Beer Awards\r\n\r\nWorld's 50 Best Beers\r\n2006 International Beer Challenge\r\n\r\nGold Medal, 92 Points\r\n2006 World Beer Championships\r\n\r\nSilver Medal, India Pale Ale Category\r\n2006 Australian International Beer Awards\r\n\r\nModern Brewery Age, February 12, 2007\r\n5 out 5 stars \r\nHop Henge started its life as an India Pale Ale, but this year it was bumped up to “Imperial IPA” status, with a hefty dose of additional hops.\r\n“This one is lovely,” said taster Tom Conti. “They got it just right.”\r\nIt pours out a deep amber, with an appealing rocky head, and rich hop aroma wafting from the glass. “They sure dosed it with a lot of hops...[there’s] a lot of hop bitterness in the taste,” one taster observed.\r\nIn addition to the Imperial-level hopping, Hop Henge also boasts Imperial-level alcohol content, with 8.1% a.b.v.\r\nThis was the top-rated beer during its tasting session, and tasters had to dig deep for new superlatives to describe it. “This is a beautiful beer,” concluded taster Gregg Glaser. “Full of flavor and hops and malt and hops again.”\r\n“Not for the timid,” said taster Robert Lachman.\r\n\r\n-http://www.deschutesbrewery.com/BrewPub/OnTap/125352.aspx","style":"Imperial or Double India Pale Ale","category":"North American Ale"}, "dux_de_lux-ginger_tom": {"name":"Ginger Tom","abv":4.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"dux_de_lux","updated":"2010-07-22 20:00:20","description":"An old fashioned style ginger beer brewed naturally with honey, malt, lemon and fresh root ginger, leaving you with a refreshing crisp zesty flavour.","style":"Herb and Spice Beer","category":"Other Style"}, "four_peaks_brewing-hop_knot_ipa": {"name":"Hop Knot IPA","abv":6.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"four_peaks_brewing","updated":"2010-07-22 20:00:20","description":"Our Hop Knot IPA is made only from American malt and lots of American hops, which produce a big, broad-shouldered, hoppy beer backed up by a character as warm as, well, Mom and apple pie… \r\n\r\nHop Knot IPA get its peculiar name from the weaving of four different hops added at four separate times during the brewing process. Including our cavernous hop-back, which gets so stuffed with whole leaf hops that we feel genuine guilt for its excess. Hop Knot is an ale that is to be enjoyed with friends, spicy food or any time you need a good hop fix without the harsh bitterness. We hope you enjoy this pioneering beer made in the bold spirit of Americans everywhere. \r\n\r\nAlcohol content approximately 6.0% by volume (ALWAYS ON TAP!!)","style":"American-Style India Pale Ale","category":"North American Ale"}, - "fratellos_restaurant_and_brewery-fox_river_golden_ale": {"name":"Fox River Golden Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"fratellos_restaurant_and_brewery","updated":"2010-07-22 20:00:20","description":""}, + "fratellos_restaurant_and_brewery-fox_river_golden_ale": {"name":"Fox River Golden Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"fratellos_restaurant_and_brewery","updated":"2010-07-22 20:00:20","description":"Fox River Golden Ale by Fratello's Restaurant and Brewery is a well-crafted, approachable ale that exemplifies the brewery’s commitment to producing flavorful and drinkable beers. Located in Wisconsin, Fratello’s combines its restaurant experience with brewing to create a unique dining and drinking environment."}, "great_divide_brewing-fresh_hop_pale_ale": {"name":"Fresh Hop Pale Ale","abv":6.1,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"great_divide_brewing","updated":"2010-07-22 20:00:20","description":"The September hop harvest is a once-a-year opportunity to brew with fresh hops, also called “wet hops.” Given the perishable nature of just-harvested hop cones, they are shipped overnight to Great Divide shortly after harvest. The morning of the scheduled hop delivery in Denver, Great Divide’s brewers begin brewing Fresh Hop and are ready to hop the beer just as the fresh hops are delivered.\r\n\r\nUsing fresh hops is a big endeavor, requiring four to five times the volume of hops compared to the usual process of using pelletized hops. This complex process brings impressive results: Fresh Hop is an American-Style Pale Ale with moderate hop bitterness marked by a unique and intensely grassy hop flavor and aroma. Fresh Hop is a superbly refreshing, medium bodied, light-copper colored pale ale.","style":"American-Style Pale Ale","category":"North American Ale"}, - "green_bay_brewing": {"name":"Green Bay Brewing","city":"Green Bay","state":"Wisconsin","code":"54303","country":"United States","phone":"1-888-604-2337","website":"","type":"brewery","updated":"2010-07-22 20:00:20","description":"","address":["313 Dousman Street"],"geo":{"accuracy":"ROOFTOP","lat":44.5189,"lon":-88.0196}}, - "green_flash_brewing": {"name":"Green Flash Brewing","city":"Vista","state":"California","code":"92083","country":"United States","phone":"1-760-597-9012","website":"http://www.greenflashbrew.com","type":"brewery","updated":"2010-07-22 20:00:20","description":"","address":["1430 Vantage Court #104A"],"geo":{"accuracy":"ROOFTOP","lat":33.136,"lon":-117.225}}, - "green_mill_brewing_saint_paul": {"name":"Green Mill Brewing - Saint Paul","city":"Saint Paul","state":"Minnesota","code":"55105","country":"United States","phone":"1-651-698-0353","website":"","type":"brewery","updated":"2010-07-22 20:00:20","description":"","address":["57 Hamline Avenue South"],"geo":{"accuracy":"RANGE_INTERPOLATED","lat":44.9398,"lon":-93.1568}}, + "green_bay_brewing": {"name":"Green Bay Brewing","city":"Green Bay","state":"Wisconsin","code":"54303","country":"United States","phone":"1-888-604-2337","website":"","type":"brewery","updated":"2010-07-22 20:00:20","description":"Green Bay Brewing, also known as Titletown Brewing Company, is a well-known brewery located in Green Bay, Wisconsin. It is celebrated for crafting high-quality, flavorful beers that reflect the spirit of the region and the city’s deep-rooted connection to football. The brewery was initially housed in a historic downtown train depot, adding to its charm and connection to the city's history.","address":["313 Dousman Street"],"geo":{"accuracy":"ROOFTOP","lat":44.5189,"lon":-88.0196}}, + "green_flash_brewing": {"name":"Green Flash Brewing","city":"Vista","state":"California","code":"92083","country":"United States","phone":"1-760-597-9012","website":"http://www.greenflashbrew.com","type":"brewery","updated":"2010-07-22 20:00:20","description":"Green Flash Brewing Company is a well-known craft brewery based in San Diego, California, celebrated for its innovative and hop-forward beers. Founded in 2002 by Mike and Lisa Hinkley, Green Flash has established itself as a pioneer in the craft beer movement, particularly in the realm of India Pale Ales (IPAs).","address":["1430 Vantage Court #104A"],"geo":{"accuracy":"ROOFTOP","lat":33.136,"lon":-117.225}}, + "green_mill_brewing_saint_paul": {"name":"Green Mill Brewing - Saint Paul","city":"Saint Paul","state":"Minnesota","code":"55105","country":"United States","phone":"1-651-698-0353","website":"","type":"brewery","updated":"2010-07-22 20:00:20","description":"Green Mill Brewing in Saint Paul, Minnesota, is a historically significant brewery that played a key role in the craft beer movement in the region. Originally established as part of the Green Mill Restaurant, which began as a small neighborhood pizzeria in 1935, Green Mill Brewing quickly became known for its innovative approach to brewing, combining traditional techniques with modern experimentation.","address":["57 Hamline Avenue South"],"geo":{"accuracy":"RANGE_INTERPOLATED","lat":44.9398,"lon":-93.1568}}, "green_mountain_beverage": {"name":"Green Mountain Beverage","city":"Middlebury","state":"Vermont","code":"5753","country":"United States","phone":"1-802-873-9045","website":"http://www.gmbeverage.com/","type":"brewery","updated":"2010-07-22 20:00:20","description":"Green Mountain Beverage is nestled in a valley near the historic Green Mountains in Middlebury, Vermont. This is where we operate our Green Mountain Cidery (our own word we created from a derivative of brewery) and we have become the number one producer of premium alcoholic draft ciders in the U.S. Throughout these pristine areas of the Green Mountains, people are committed to preserving nature's beauty and still today carry on the long standing tradition of taking pride in their work and the products they create which are truly special. Here at Green Mountain Beverage, we also carry on these traditions. Our ciders are a unique combination of nature's best ingredients, our skills, commitment and pride in our work.","address":["153 Pond Lane"],"geo":{"accuracy":"ROOFTOP","lat":44.0344,"lon":-73.1735}}, "harpoon_brewery_boston-glacier_harvest_09_wet_hop_100_barrel_series_28": {"name":"Glacier Harvest '09 Wet Hop (100 Barrel Series #28)","abv":6.7,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"harpoon_brewery_boston","updated":"2010-07-22 20:00:20","description":"For the 28th session of the Harpoon 100 Barrel Series, we’re celebrating this year’s hop harvest with Glacier Harvest Wet Hop beer, a pale ale made with fresh Glacier hops.\r\n \r\nWet hop beers are brewed using fresh, “wet” hops instead of traditional dried hops—hops contain about 60% moisture when they are first picked.\r\n\r\nTypically, when hops are picked they are quickly dried and refrigerated to increase shelf life and make them more consistent for brewing. Freshly picked wet hops, however, need to be used within hours of harvest or they will begin to degrade rapidly. Wet hops retain more of their natural aroma and volatile flavors that dissipate when dried. This gives wet hop beers a fresher hop flavor and aroma than that of beers hopped with processed hops.\r\nThis yields an immersed, intense hop flavor in the beer.\r\n\r\nHarpoon brewer Ray Dobens, creator of the beer, added a heroic dose of fresh hops the day of the harvest.The hop flavor and aroma from this copper-colored ale comes from a generous late addition of freshly harvested “wet” hops.","style":"American-Style Pale Ale","category":"North American Ale"}, "left_coast_brewing-hop_juice_double_ipa": {"name":"Hop Juice Double IPA","abv":9.4,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"left_coast_brewing","updated":"2010-07-22 20:00:20","description":"Here at Left Coast Brewing Co. we pride ourselves on being one of the first breweries to pioneer a Double IPA style beer. In 2003, we brewed our first Double IPA, and haven't looked back since. This hop monster uses Premium American 2- Row and a touch of light crystal malt to create a solid malt foundation. The recipe calls for hops to be used in every step of the brewing process; in the mash, in a hop back, in the fermenter, and in the bright tanks. We use hop extract, hop pellets and hop flowers, hence the name Hop Juice. Hop Juice spends more than 4 weeks dry hopping in the fermenter and the bright beer tank. It is approximately 9.4% abv and has massive IBUs. Hop usage is over 4lbs per barrel. Hopeheads, step up to the plate!","style":"Imperial or Double India Pale Ale","category":"North American Ale"}, "lift_bridge_brewery-harvestor_fresh_hop_ale": {"name":"Harvestör Fresh Hop Ale","abv":7.2,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"lift_bridge_brewery","updated":"2010-07-22 20:00:20","description":"Constant addition of fresh Cascade and Willamette hops from Brad’s Stillwater hop garden during boil and fermentation process. Aromatic and caramel malts compliment the hop character for an incredibly balanced beer that celebrates the hop harvest without killing your tongue.","style":"American-Style Pale Ale","category":"North American Ale"}, - "martini_brauerei-weissbier_dunkel": {"name":"Weissbier Dunkel","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"martini_brauerei","updated":"2010-07-22 20:00:20","description":""}, - "mcmenamins_mill_creek-irish_stout": {"name":"Irish Stout","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"mcmenamins_mill_creek","updated":"2010-07-22 20:00:20","description":"","style":"American-Style Stout","category":"North American Ale"}, - "miller_brewing-miller_genuine_draft_64": {"name":"Miller Genuine Draft 64","abv":2.8,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"miller_brewing","updated":"2010-07-22 20:00:20","description":"","style":"American-Style Light Lager","category":"North American Lager"}, - "new_jersey_beer_company-hudson_pale_ale": {"name":"Hudson Pale Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"new_jersey_beer_company","updated":"2010-11-17 14:13:17","description":"","style":"American-Style Pale Ale","category":"North American Ale"}, - "nicolet_brewing-blonde": {"name":"Blonde","abv":3.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"nicolet_brewing","updated":"2010-07-22 20:00:20","description":""}, + "martini_brauerei-weissbier_dunkel": {"name":"Weissbier Dunkel","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"martini_brauerei","updated":"2010-07-22 20:00:20","description":"Weissbier Dunkel by Martini Brauerei is a dark wheat beer that blends the traditional elements of a classic German wheat beer with the deeper, roasted flavors of darker malts. Martini Brauerei, based in Germany, is known for its dedication to authentic German brewing traditions, and their Weissbier Dunkel reflects that heritage."}, + "mcmenamins_mill_creek-irish_stout": {"name":"Irish Stout","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"mcmenamins_mill_creek","updated":"2010-07-22 20:00:20","description":"Irish Stout by McMenamins Mill Creek is a rich, dark ale that embodies the classic characteristics of a traditional Irish stout while incorporating the distinctive approach of McMenamins, known for its unique craft beers brewed in various locations throughout the Pacific Northwest. McMenamins Mill Creek, located in Washington, is part of the larger McMenamins chain of brewpubs, and their Irish Stout is a seasonal favorite.","style":"American-Style Stout","category":"North American Ale"}, + "miller_brewing-miller_genuine_draft_64": {"name":"Miller Genuine Draft 64","abv":2.8,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"miller_brewing","updated":"2010-07-22 20:00:20","description":"Miller Genuine Draft 64 (MGD 64) by Miller Brewing Company is a light American lager specifically crafted for those looking for a low-calorie, easy-drinking beer. Designed as a refreshing and low-carb option, it’s known for being one of the lightest beers in the market, both in flavor and caloric content.","style":"American-Style Light Lager","category":"North American Lager"}, + "new_jersey_beer_company-hudson_pale_ale": {"name":"Hudson Pale Ale","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"new_jersey_beer_company","updated":"2010-11-17 14:13:17","description":"Hudson Pale Ale by New Jersey Beer Company is a flavorful, American-style pale ale that showcases the brewery's commitment to crafting high-quality beers with a focus on freshness and local ingredients. Known for its balanced profile, Hudson Pale Ale is a favorite among those who appreciate a hoppy yet approachable beer.","style":"American-Style Pale Ale","category":"North American Ale"}, + "nicolet_brewing-blonde": {"name":"Blonde","abv":3.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"nicolet_brewing","updated":"2010-07-22 20:00:20","description":"Blonde by Nicolet Brewing is a delightful and easy-drinking blonde ale that embodies the brewery's commitment to crafting approachable yet flavorful beers. Located in Wisconsin, Nicolet Brewing focuses on producing high-quality, small-batch brews that reflect the character of the region."}, "odell_brewing-5_barrel_pale_ale": {"name":"5 Barrel Pale Ale","abv":5.3,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"odell_brewing","updated":"2010-07-22 20:00:20","description":"The distinctive hop character of our 5 Barrel Pale Ale is due to the extraction of essential oils from select hops. We treat 5 Barrel Pale Ale to an infusion of fresh whole hop flowers in the Hop Back and the Fermentor, as well as four hop additions during the kettle boil. We like how this gives the beer a fresh, lively flavor and aroma.","style":"Classic English-Style Pale Ale","category":"British Ale"}, - "qingdao_brewery-green_beer": {"name":"Green Beer","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"qingdao_brewery","updated":"2010-07-22 20:00:20","description":"","style":"American-Style Lager","category":"North American Lager"}, + "qingdao_brewery-green_beer": {"name":"Green Beer","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"qingdao_brewery","updated":"2010-07-22 20:00:20","description":"Green pilsner colored by spirulina and claimed to promote good health","style":"American-Style Lager","category":"North American Lager"}, "river_horse_brewing_company-hop_hazard": {"name":"Hop Hazard","abv":5.5,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"river_horse_brewing_company","updated":"2010-07-22 20:00:20","description":"Brewing the perfect ale is truly a balancing act...hazardous work you might say. With Hop Hazard our challenge was to hand craft a malt rich base that could counterbalance a combustible five-hop blend and still leave your taste buds with enough room to enjoy a unique, crisp hop finish.","style":"American-Style Pale Ale","category":"North American Ale"}, - "russian_river_brewing-dead_leaf_green": {"name":"Dead Leaf Green","abv":5.68,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"russian_river_brewing","updated":"2010-07-22 20:00:20","description":"","style":"Special Bitter or Best Bitter","category":"British Ale"}, + "russian_river_brewing-dead_leaf_green": {"name":"Dead Leaf Green","abv":5.68,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"russian_river_brewing","updated":"2010-07-22 20:00:20","description":"An English style Extra Special Bitter with rich notes of malt and earthy English and European hops.","style":"Special Bitter or Best Bitter","category":"British Ale"}, "smuttynose_brewing_co-smuttynose_pumpkin_ale": {"name":"Smuttynose Pumpkin Ale","abv":5.1,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"smuttynose_brewing_co","updated":"2010-07-22 20:00:20","description":"Smuttynose Pumpkin Ale is our homage to the craft and heritage of America’s brewers. Recipes calling for the use of pumpkins in beer date back to early colonial times, when brewers sought to extend their supply of costly imported malt with locally grown ingredients, such as squash and “pompions.”\r\n\r\nIn that spirit, we brew our ale with the addition of pumpkin to the mash, along with traditional spices to create a delicious American original.","style":"Pumpkin Beer","category":"Other Style"}, "southern_tier_brewing_co-hop_sun": {"name":"Hop Sun","abv":4.5,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"southern_tier_brewing_co","updated":"2010-07-22 20:00:20","description":"Pour Hop Sun Summer Wheat Beer into a pint glass, give it a long whiff and you’ll realize that this isn’t your average wheatbeer. Filtered to a golden clarity and dry-hopped to perfection, Hop Sun is a fantastic session ale in which flavors of wheat, barley and hops commingle to a refreshing and zesty conclusion. Hints of lemon and sweet malts waft to the fore as a touch of bitterness contributes to Hop Sun’s bright finish. Enjoy Hop Sun all summer long as a perfect balance to your outdoor recreation. Summer never tasted so good.","style":"Light American Wheat Ale or Lager","category":"Other Style"}, - "straub_brewery-straub_light": {"name":"Straub Light","abv":3.16,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"straub_brewery","updated":"2010-07-22 20:00:20","description":""}, + "straub_brewery-straub_light": {"name":"Straub Light","abv":3.16,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"straub_brewery","updated":"2010-07-22 20:00:20","description":"Straub Light by Straub Brewery is a light lager that offers a refreshing and approachable drinking experience, perfect for those seeking a lower-calorie option without sacrificing flavor. Established in 1872, Straub Brewery is known for its commitment to traditional brewing techniques and high-quality ingredients."}, "tallgrass_brewing_co-oasis": {"name":"Oasis","abv":7.2,"ibu":93.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"tallgrass_brewing_co","updated":"2010-11-11 19:32:59","description":"Oasis is a Double ESB/IPAish beer that came about from playing around with one of Jeff","style":"American-Style India Pale Ale","category":"North American Ale"}, "thirsty_dog_brewing-stud_service_stout": {"name":"Stud Service Stout","abv":3.1,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"thirsty_dog_brewing","updated":"2010-07-22 20:00:20","description":"A traditional dry Irish Stout. Very Flavorful while low in calories and alcohol. An easy drinking “session beer” that has a nice caramel flavor an smooth finish.","style":"American-Style Stout","category":"North American Ale"}, - "thirstybear_brewing-valencia_wheat": {"name":"Valencia Wheat","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"thirstybear_brewing","updated":"2010-07-22 20:00:20","description":""}, - "tyskie_browary_ksiazece-tyskie_gronie": {"name":"Tyskie Gronie","abv":5.7,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"tyskie_browary_ksiazece","updated":"2010-07-22 20:00:20","description":""}, + "thirstybear_brewing-valencia_wheat": {"name":"Valencia Wheat","abv":0.0,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"thirstybear_brewing","updated":"2010-07-22 20:00:20","description":"Valencia Wheat by ThirstyBear Brewing is a refreshing and flavorful wheat beer that pays homage to the traditional styles while incorporating unique elements that highlight its craft brewing heritage. ThirstyBear, based in San Francisco, is known for its commitment to using high-quality ingredients and local flavors, and Valencia Wheat exemplifies this approach."}, + "tyskie_browary_ksiazece-tyskie_gronie": {"name":"Tyskie Gronie","abv":5.7,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"tyskie_browary_ksiazece","updated":"2010-07-22 20:00:20","description":"Tyskie Gronie is a classic Polish lager with a long history and widespread popularity both in Poland and internationally. Brewed by Tyskie Browary Książęce, one of Poland's oldest breweries, this pale lager is known for its smooth, balanced taste and high drinkability."}, "victory_brewing-yakima_glory": {"name":"Yakima Glory","abv":8.7,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"victory_brewing","updated":"2010-11-27 12:29:23","description":"The tenacious grip of big, juicy hop aroma and character slides smoothly into rich, dark malts. This heavyweight battle between fresh, Yakima Valley hops and dark, roasted malts is resolved harmoniously as the flavors merge to deliver complex satisfaction with a warming edge. Bask in the ","style":"American-Style India Black Ale","category":"North American Ale"}, "wachusetts_brewing_company-green_monsta_ale": {"name":"Green Monsta Ale","abv":7.3,"ibu":0.0,"srm":0.0,"upc":0,"type":"beer","brewery_id":"wachusetts_brewing_company","updated":"2010-07-22 20:00:20","description":"A BIG PALE ALE with an awsome balance of Belgian malts with Fuggles and East Kent Golding hops.","style":"American-Style Strong Pale Ale","category":"North American Ale"} } diff --git a/tests/beer-search.json b/tests/beer-search.json index 6db5d94a..62ebd100 100644 --- a/tests/beer-search.json +++ b/tests/beer-search.json @@ -26,7 +26,7 @@ "default_type": "_default", "docvalues_dynamic": true, "index_dynamic": true, - "store_dynamic": false, + "store_dynamic": true, "type_field": "type", "types": { "beer": {