|
@@ -0,0 +1,194 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace Webkul\BagistoApi\Tests\Feature;
|
|
|
|
|
+
|
|
|
|
|
+use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
|
|
+use Illuminate\Testing\TestResponse;
|
|
|
|
|
+use Tests\TestCase;
|
|
|
|
|
+use Webkul\BagistoApi\Models\StorefrontKey;
|
|
|
|
|
+use Webkul\BagistoApi\Http\Middleware\LogApiRequests;
|
|
|
|
|
+
|
|
|
|
|
+class ProductSearchGraphQLTest extends TestCase
|
|
|
|
|
+{
|
|
|
|
|
+ use DatabaseTransactions;
|
|
|
|
|
+
|
|
|
|
|
+ private string $storefrontKey;
|
|
|
|
|
+
|
|
|
|
|
+ protected function setUp(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ parent::setUp();
|
|
|
|
|
+
|
|
|
|
|
+ $this->withoutMiddleware(LogApiRequests::class);
|
|
|
|
|
+
|
|
|
|
|
+ $this->storefrontKey = StorefrontKey::generateKey();
|
|
|
|
|
+
|
|
|
|
|
+ StorefrontKey::query()->create([
|
|
|
|
|
+ 'name' => 'Product Search GraphQL Test',
|
|
|
|
|
+ 'key' => $this->storefrontKey,
|
|
|
|
|
+ 'is_active' => true,
|
|
|
|
|
+ 'rate_limit' => 1000,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ if (
|
|
|
|
|
+ core()->getConfigData('catalog.products.search.engine') !== 'elastic'
|
|
|
|
|
+ || core()->getConfigData('catalog.products.search.storefront_mode') !== 'elastic'
|
|
|
|
|
+ ) {
|
|
|
|
|
+ $this->markTestSkipped('The product search GraphQL feature requires the Elasticsearch storefront mode.');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function graphQL(string $query, array $variables = []): TestResponse
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->postJson('/api/graphql', [
|
|
|
|
|
+ 'query' => $query,
|
|
|
|
|
+ 'variables' => $variables,
|
|
|
|
|
+ ], [
|
|
|
|
|
+ 'X-STOREFRONT-KEY' => $this->storefrontKey,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_search_returns_correction_metadata_inside_data_and_logs_once(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $before = DB::table('search_log')->count();
|
|
|
|
|
+
|
|
|
|
|
+ $response = $this->graphQL(<<<'GQL'
|
|
|
|
|
+ query ProductSearchCorrection {
|
|
|
|
|
+ search(query: "readyy", suggest: true, first: 1) {
|
|
|
|
|
+ totalCount
|
|
|
|
|
+ edges {
|
|
|
|
|
+ cursor
|
|
|
|
|
+ node { id sku name }
|
|
|
|
|
+ }
|
|
|
|
|
+ pageInfo {
|
|
|
|
|
+ hasNextPage
|
|
|
|
|
+ hasPreviousPage
|
|
|
|
|
+ startCursor
|
|
|
|
|
+ endCursor
|
|
|
|
|
+ }
|
|
|
|
|
+ searchMetadata {
|
|
|
|
|
+ originalQuery
|
|
|
|
|
+ effectiveQuery
|
|
|
|
|
+ resultMessage
|
|
|
|
|
+ searchInsteadMessage
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ GQL);
|
|
|
|
|
+
|
|
|
|
|
+ $response
|
|
|
|
|
+ ->assertOk()
|
|
|
|
|
+ ->assertJsonPath('data.search.searchMetadata.originalQuery', 'readyy')
|
|
|
|
|
+ ->assertJsonPath('data.search.searchMetadata.effectiveQuery', 'ready')
|
|
|
|
|
+ ->assertJsonMissingPath('extensions.searchMetadata');
|
|
|
|
|
+
|
|
|
|
|
+ $total = $response->json('data.search.totalCount');
|
|
|
|
|
+
|
|
|
|
|
+ if (! is_int($total) || $total < 1) {
|
|
|
|
|
+ $this->markTestSkipped('The Elasticsearch test index does not contain products matching "ready".');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $response->assertJsonStructure([
|
|
|
|
|
+ 'data' => [
|
|
|
|
|
+ 'search' => [
|
|
|
|
|
+ 'edges' => [['cursor', 'node' => ['id', 'sku', 'name']]],
|
|
|
|
|
+ 'pageInfo' => ['hasNextPage', 'hasPreviousPage', 'startCursor', 'endCursor'],
|
|
|
|
|
+ ],
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertSame($before + 1, DB::table('search_log')->count());
|
|
|
|
|
+ $this->assertDatabaseHas('search_log', [
|
|
|
|
|
+ 'search_text' => 'readyy',
|
|
|
|
|
+ 'query_text' => 'ready',
|
|
|
|
|
+ 'results' => $total,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_search_without_suggest_returns_null_metadata_and_does_not_log(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $before = DB::table('search_log')->count();
|
|
|
|
|
+
|
|
|
|
|
+ $response = $this->graphQL(<<<'GQL'
|
|
|
|
|
+ query ProductSearchWithoutSuggest {
|
|
|
|
|
+ search(query: "readyy", suggest: false, first: 1) {
|
|
|
|
|
+ totalCount
|
|
|
|
|
+ searchMetadata { effectiveQuery }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ GQL);
|
|
|
|
|
+
|
|
|
|
|
+ $response
|
|
|
|
|
+ ->assertOk()
|
|
|
|
|
+ ->assertJsonPath('data.search.searchMetadata', null);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertIsInt($response->json('data.search.totalCount'));
|
|
|
|
|
+ $this->assertSame($before, DB::table('search_log')->count());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_search_preserves_filtering_and_cursor_pagination(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $page = $this->graphQL(<<<'GQL'
|
|
|
|
|
+ query ProductSearchPage {
|
|
|
|
|
+ search(query: "ready", first: 1) {
|
|
|
|
|
+ totalCount
|
|
|
|
|
+ edges { cursor node { sku } }
|
|
|
|
|
+ pageInfo { hasNextPage hasPreviousPage }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ GQL);
|
|
|
|
|
+
|
|
|
|
|
+ $page
|
|
|
|
|
+ ->assertOk()
|
|
|
|
|
+ ->assertJsonPath('data.search.pageInfo.hasPreviousPage', false);
|
|
|
|
|
+
|
|
|
|
|
+ $total = $page->json('data.search.totalCount');
|
|
|
|
|
+ $cursor = $page->json('data.search.edges.0.cursor');
|
|
|
|
|
+ $firstSku = $page->json('data.search.edges.0.node.sku');
|
|
|
|
|
+
|
|
|
|
|
+ if (! is_int($total) || $total < 1 || ! is_string($cursor) || ! is_string($firstSku)) {
|
|
|
|
|
+ $this->markTestSkipped('The Elasticsearch test index does not contain products matching "ready".');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $page->assertJsonPath('data.search.pageInfo.hasNextPage', $total > 1);
|
|
|
|
|
+
|
|
|
|
|
+ if ($total > 1) {
|
|
|
|
|
+ $nextPage = $this->graphQL(
|
|
|
|
|
+ <<<'GQL'
|
|
|
|
|
+ query ProductSearchNextPage($after: String!) {
|
|
|
|
|
+ search(query: "ready", first: 1, after: $after) {
|
|
|
|
|
+ totalCount
|
|
|
|
|
+ edges { node { sku } }
|
|
|
|
|
+ pageInfo { hasNextPage hasPreviousPage }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ GQL,
|
|
|
|
|
+ ['after' => $cursor]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ $nextPage
|
|
|
|
|
+ ->assertOk()
|
|
|
|
|
+ ->assertJsonPath('data.search.totalCount', $total)
|
|
|
|
|
+ ->assertJsonPath('data.search.pageInfo.hasPreviousPage', true);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertNotSame($firstSku, $nextPage->json('data.search.edges.0.node.sku'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $filtered = $this->graphQL(
|
|
|
|
|
+ <<<'GQL'
|
|
|
|
|
+ query ProductSearchFilter($filter: String!) {
|
|
|
|
|
+ search(query: "ready", filter: $filter, first: 10) {
|
|
|
|
|
+ totalCount
|
|
|
|
|
+ edges { node { sku } }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ GQL,
|
|
|
|
|
+ ['filter' => json_encode(['sku' => $firstSku])]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ $filtered
|
|
|
|
|
+ ->assertOk()
|
|
|
|
|
+ ->assertJsonPath('data.search.totalCount', 1)
|
|
|
|
|
+ ->assertJsonPath('data.search.edges.0.node.sku', $firstSku);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|