diff --git a/retail/snippets/README.md b/retail/snippets/README.md new file mode 100644 index 00000000000..9c44aecbfae --- /dev/null +++ b/retail/snippets/README.md @@ -0,0 +1,26 @@ +# Vertex AI Search for commerce Samples + +This directory contains Java samples for [Vertex AI Search for commerce](https://cloud.google.com/retail/docs/search-basic#search). + +## Prerequisites + +To run these samples, you must have: + +1. **A Google Cloud Project** with the [Vertex AI Search for commerce API](https://console.cloud.google.com/apis/library/retail.googleapis.com) enabled. +2. **Vertex AI Search for commerce** set up with a valid catalog and serving configuration (placement). +3. **Authentication**: These samples use [Application Default Credentials (ADC)](https://cloud.google.com/docs/authentication/provide-credentials-adc). + - If running locally, you can set up ADC by running: + ```bash + gcloud auth application-default login + ``` +4. **IAM Roles**: The service account or user running the samples needs the `roles/retail.viewer` (Retail Viewer) role or higher. + +## Samples + +- **[Search.java](src/main/java/com/example/search/Search.java)**: Basic search request showing both text search and browse search (using categories). +- **[SearchPagination.java](src/main/java/com/example/search/SearchPagination.java)**: Shows how to use `next_page_token` to paginate through search results. +- **[SearchOffset.java](src/main/java/com/example/search/SearchOffset.java)**: Shows how to use `offset` to skip a specified number of results. + +## Documentation + +For more information, see the [Vertex AI Search for commerce documentation](https://docs.cloud.google.com/retail/docs/search-basic#search). \ No newline at end of file diff --git a/retail/snippets/pom.xml b/retail/snippets/pom.xml new file mode 100644 index 00000000000..0066e78fb56 --- /dev/null +++ b/retail/snippets/pom.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + com.example.retail + retail-samples + 1.0-SNAPSHOT + + + + shared-configuration + com.google.cloud.samples + 1.2.2 + + + + 21 + 21 + + + + + + libraries-bom + com.google.cloud + import + pom + 26.80.0 + + + + + + + com.google.cloud + google-cloud-retail + + + + + truth + com.google.truth + test + 1.4.5 + + + org.junit.jupiter + junit-jupiter + 5.14.3 + test + + + diff --git a/retail/snippets/src/main/java/com/example/search/Search.java b/retail/snippets/src/main/java/com/example/search/Search.java new file mode 100644 index 00000000000..42305cb1000 --- /dev/null +++ b/retail/snippets/src/main/java/com/example/search/Search.java @@ -0,0 +1,85 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +// [START retail_v2_search_request] + +import com.google.cloud.retail.v2.BranchName; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchResponse.SearchResult; +import com.google.cloud.retail.v2.SearchServiceClient; +import com.google.cloud.retail.v2.SearchServiceClient.SearchPagedResponse; +import com.google.cloud.retail.v2.ServingConfigName; +import java.io.IOException; +import java.util.List; + +public class Search { + public static void main(String[] args) throws IOException { + String projectId = "my-project-id"; + String placementId = "default_search"; + String visitorId = "my-visitor-id"; + String query = "my search query"; + List categories = List.of("category"); + + search(projectId, placementId, visitorId, query, categories); + } + + /** + * Search for products using Vertex AI Search for commerce. + * + * Performs a search request for a specific placement. Handles both text search (using query) + * and browse search (using page_categories). + * + * @param projectId The Google Cloud project ID. + * @param placementId The placement name for the search. + * @param visitorId A unique identifier for the user. + * @param query The search term for text search. + * @param categories The categories for browse search. + */ + public static void search( + String projectId, String placementId, String visitorId, String query, List categories) + throws IOException { + try (SearchServiceClient searchServiceClient = SearchServiceClient.create()) { + ServingConfigName placementName = + ServingConfigName.of(projectId, "global", "default_catalog", placementId); + BranchName branchName = + BranchName.of(projectId, "global", "default_catalog", "default_branch"); + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(placementName.toString()) + .setBranch(branchName.toString()) + .setVisitorId(visitorId) + .setQuery(query) + .addAllPageCategories(categories) + .setPageSize(10) + .build(); + SearchPagedResponse response = searchServiceClient.search(searchRequest); + + SearchResponse searchResponse = response.getPage().getResponse(); + + System.out.println("Found " + searchResponse.getResultsCount() + " results in current page"); + for (SearchResult searchResult : searchResponse.getResultsList()) { + Product product = searchResult.getProduct(); + System.out.println("---- Search Result ----"); + System.out.println("Product Name: " + product.getName()); + } + } + } +} +// [END retail_v2_search_request] diff --git a/retail/snippets/src/main/java/com/example/search/SearchOffset.java b/retail/snippets/src/main/java/com/example/search/SearchOffset.java new file mode 100644 index 00000000000..df9cbff4ecb --- /dev/null +++ b/retail/snippets/src/main/java/com/example/search/SearchOffset.java @@ -0,0 +1,83 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +// [START retail_v2_search_offset] + +import com.google.cloud.retail.v2.BranchName; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchResponse.SearchResult; +import com.google.cloud.retail.v2.SearchServiceClient; +import com.google.cloud.retail.v2.SearchServiceClient.SearchPagedResponse; +import com.google.cloud.retail.v2.ServingConfigName; +import java.io.IOException; + +public class SearchOffset { + public static void main(String[] args) throws IOException { + String projectId = "my-project-id"; + String placementId = "default_search"; + String visitorId = "my-visitor-id"; + String query = "my search query"; + int offset = 10; + + searchWithOffset(projectId, placementId, visitorId, query, offset); + } + + /** + * Search for products with an offset using Vertex AI Search for commerce. + * + * Performs a search request starting from a specified position. + * + * @param projectId The Google Cloud project ID. + * @param placementId The placement name for the search. + * @param visitorId A unique identifier for the user. + * @param query The search term for text search. + * @param offset The number of results to skip. + */ + public static void searchWithOffset( + String projectId, String placementId, String visitorId, String query, int offset) + throws IOException { + try (SearchServiceClient searchServiceClient = SearchServiceClient.create()) { + ServingConfigName placementName = + ServingConfigName.of(projectId, "global", "default_catalog", placementId); + BranchName branchName = + BranchName.of(projectId, "global", "default_catalog", "default_branch"); + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(placementName.toString()) + .setBranch(branchName.toString()) + .setVisitorId(visitorId) + .setQuery(query) + .setPageSize(10) + .setOffset(offset) + .build(); + SearchPagedResponse response = searchServiceClient.search(searchRequest); + + SearchResponse searchResponse = response.getPage().getResponse(); + + System.out.println("Found " + searchResponse.getResultsCount() + " results in current page"); + for (SearchResult searchResult : searchResponse.getResultsList()) { + Product product = searchResult.getProduct(); + System.out.println("---- Search Result ----"); + System.out.println("Product Name: " + product.getName()); + } + } + } +} +// [END retail_v2_search_offset] diff --git a/retail/snippets/src/main/java/com/example/search/SearchPagination.java b/retail/snippets/src/main/java/com/example/search/SearchPagination.java new file mode 100644 index 00000000000..1ef5534bf2d --- /dev/null +++ b/retail/snippets/src/main/java/com/example/search/SearchPagination.java @@ -0,0 +1,94 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +// [START retail_v2_search_pagination] + +import com.google.cloud.retail.v2.BranchName; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse.SearchResult; +import com.google.cloud.retail.v2.SearchServiceClient; +import com.google.cloud.retail.v2.SearchServiceClient.SearchPage; +import com.google.cloud.retail.v2.SearchServiceClient.SearchPagedResponse; +import com.google.cloud.retail.v2.ServingConfigName; +import java.io.IOException; + +public class SearchPagination { + public static void main(String[] args) throws IOException { + String projectId = "my-project-id"; + String placementId = "default_search"; + String visitorId = "my-visitor-id"; + String query = "my search query"; + int pageSize = 10; + + searchWithPagination(projectId, placementId, visitorId, query, pageSize); + } + + /** + * Search for products with pagination using Vertex AI Search for commerce. + * + * Performs a search request, then uses the next_page_token to get the next page. + * + * @param projectId The Google Cloud project ID. + * @param placementId The placement name for the search. + * @param visitorId A unique identifier for the user. + * @param query The search term for text search. + * @param pageSize The amount of results per page. + */ + public static void searchWithPagination( + String projectId, String placementId, String visitorId, String query, int pageSize) + throws IOException { + try (SearchServiceClient searchServiceClient = SearchServiceClient.create()) { + ServingConfigName placementName = + ServingConfigName.of(projectId, "global", "default_catalog", placementId); + BranchName branchName = + BranchName.of(projectId, "global", "default_catalog", "default_branch"); + SearchRequest request = + SearchRequest.newBuilder() + .setPlacement(placementName.toString()) + .setBranch(branchName.toString()) + .setVisitorId(visitorId) + .setQuery(query) + .setPageSize(pageSize) + .build(); + int currentPage = 0; + while (true) { + SearchPagedResponse response = searchServiceClient.search(request); + + SearchPage page = response.getPage(); + currentPage++; + System.out.println("\nResults of page number " + currentPage + ":"); + System.out.println( + "Found " + page.getResponse().getResultsCount() + " results in current page"); + for (SearchResult searchResult : page.getResponse().getResultsList()) { + Product product = searchResult.getProduct(); + System.out.println("---- Search Result ----"); + System.out.println("Product Name: " + product.getName()); + } + + if (page.hasNextPage()) { + request = request.toBuilder().setPageToken(page.getNextPageToken()).build(); + } else { + System.out.println("\nNo more available pages."); + break; + } + } + } + } +} +// [END retail_v2_search_pagination] diff --git a/retail/snippets/src/test/java/com/example/search/SearchIT.java b/retail/snippets/src/test/java/com/example/search/SearchIT.java new file mode 100644 index 00000000000..5333b9ed34f --- /dev/null +++ b/retail/snippets/src/test/java/com/example/search/SearchIT.java @@ -0,0 +1,91 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.google.cloud.retail.v2.Product; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class SearchIT { + private static PrintStream origPrintStream; + private static ByteArrayOutputStream bout; + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PLACEMENT_ID = "default_search"; + private static final String VISITOR_ID = "test_visitor"; + private static final int NUM_PRODUCTS_TO_TEST = 2; + private static final String PRODUCT_TITLE = "Hot Java Testing"; + private static final List CATEGORIES = List.of("Beverages"); + private static List productsToTest; + + private static void requireEnvVar(String varName) { + assertNotNull( + "Environment variable " + varName + " is required to perform these tests.", + System.getenv(varName)); + } + + @BeforeAll + public static void setUp() throws InterruptedException, IOException { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create products to be searched + productsToTest = Utils.createProductsToTest(NUM_PRODUCTS_TO_TEST, PROJECT_ID, PRODUCT_TITLE); + Utils.waitForProductsToBeReadyToTest(PROJECT_ID, productsToTest, PRODUCT_TITLE); + + origPrintStream = System.out; + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @AfterAll + public static void tearDown() throws IOException { + // Clean up products created for testing + Utils.deleteProducts(productsToTest); + + System.setOut(origPrintStream); + } + + @Test + public void testSearch() throws Exception { + Search.search(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, CATEGORIES); + String output = bout.toString(); + assertThat(output).contains("Found " + NUM_PRODUCTS_TO_TEST + " results in current page"); + assertThat(output).contains("Product Name:"); + } + + @Test + public void testSearchWithoutQueryString() throws Exception { + Search.search(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, "", CATEGORIES); + String output = bout.toString(); + assertThat(output).contains("Product Name:"); + } + + @Test + public void testSearch_productsNotFound() throws Exception { + Search.search(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, "INVALID_QUERY_VALUE_FOR_TEST", CATEGORIES); + String output = bout.toString(); + assertThat(output).contains("Found " + 0 + " results in current page"); + } +} diff --git a/retail/snippets/src/test/java/com/example/search/SearchOffsetIT.java b/retail/snippets/src/test/java/com/example/search/SearchOffsetIT.java new file mode 100644 index 00000000000..3c5a5fb6484 --- /dev/null +++ b/retail/snippets/src/test/java/com/example/search/SearchOffsetIT.java @@ -0,0 +1,108 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.cloud.retail.v2.Product; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class SearchOffsetIT { + private static PrintStream origPrintStream; + private static ByteArrayOutputStream bout; + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PLACEMENT_ID = "default_search"; + private static final String VISITOR_ID = "test_visitor"; + private static final int NUM_PRODUCTS_TO_TEST = 2; + private static final String PRODUCT_TITLE = "Hot Java Testing"; + private static List productsToTest; + + private static void requireEnvVar(String varName) { + assertNotNull( + "Environment variable " + varName + " is required to perform these tests.", + System.getenv(varName)); + } + + @BeforeAll + public static void setUp() throws InterruptedException, IOException { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create products to be searched + productsToTest = Utils.createProductsToTest(NUM_PRODUCTS_TO_TEST, PROJECT_ID, PRODUCT_TITLE); + Utils.waitForProductsToBeReadyToTest(PROJECT_ID, productsToTest, PRODUCT_TITLE); + + origPrintStream = System.out; + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @AfterAll + public static void tearDown() throws IOException { + // Clean up products created for testing + Utils.deleteProducts(productsToTest); + + System.setOut(origPrintStream); + } + + @Test + public void testSearchWithOffset() throws Exception { + int offset = 1; + SearchOffset.searchWithOffset(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, offset); + String output = bout.toString(); + assertThat(output) + .contains("Found " + (NUM_PRODUCTS_TO_TEST - offset) + " results in current page"); + assertThat(output).contains("Product Name:"); + } + + @Test + public void testSearchWithOffset_offsetEqualsZero() throws Exception { + int offset = 0; + SearchOffset.searchWithOffset(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, offset); + String output = bout.toString(); + assertThat(output).contains("Found " + NUM_PRODUCTS_TO_TEST + " results in current page"); + assertThat(output).contains("Product Name:"); + } + + @Test + public void testSearchWithOffset_offsetTooLarge_foundZeroResults() throws Exception { + int offset = 5; + SearchOffset.searchWithOffset(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, offset); + String output = bout.toString(); + assertThat(output).contains("Found 0 results in current page"); + } + + @Test + public void testSearchWithOffset_negativeOffset_throwsInvalidArgumentException() + throws Exception { + int offset = -1; + assertThrows( + InvalidArgumentException.class, + () -> + SearchOffset.searchWithOffset( + PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, offset)); + } +} diff --git a/retail/snippets/src/test/java/com/example/search/SearchPaginationIT.java b/retail/snippets/src/test/java/com/example/search/SearchPaginationIT.java new file mode 100644 index 00000000000..cc75b200836 --- /dev/null +++ b/retail/snippets/src/test/java/com/example/search/SearchPaginationIT.java @@ -0,0 +1,111 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.cloud.retail.v2.Product; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class SearchPaginationIT { + private static PrintStream origPrintStream; + private static ByteArrayOutputStream bout; + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PLACEMENT_ID = "default_search"; + private static final String VISITOR_ID = "test_visitor"; + private static final int NUM_PRODUCTS_TO_TEST = 2; + private static final String PRODUCT_TITLE = "Hot Java Testing"; + private static List productsToTest; + + private static void requireEnvVar(String varName) { + assertNotNull( + "Environment variable " + varName + " is required to perform these tests.", + System.getenv(varName)); + } + + @BeforeAll + public static void setUp() throws InterruptedException, IOException { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create products to be searched + productsToTest = Utils.createProductsToTest(NUM_PRODUCTS_TO_TEST, PROJECT_ID, PRODUCT_TITLE); + Utils.waitForProductsToBeReadyToTest(PROJECT_ID, productsToTest, PRODUCT_TITLE); + + origPrintStream = System.out; + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @AfterAll + public static void tearDown() throws IOException { + // Clean up products created for testing + Utils.deleteProducts(productsToTest); + + System.setOut(origPrintStream); + } + + @Test + public void testSearchWithPaginationAndMultiplePages() throws Exception { + SearchPagination.searchWithPagination(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, 1); + String output = bout.toString(); + assertThat(output).contains("Results of page number 1:"); + assertThat(output).contains("Found 1 results in current page"); + assertThat(output).contains("Results of page number 2:"); + assertThat(output).contains("Found 1 results in current page"); + assertThat(output).contains("No more available pages."); + } + + @Test + public void testSearchWithPaginationAndSinglePage() throws Exception { + SearchPagination.searchWithPagination(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, 10); + String output = bout.toString(); + assertThat(output).contains("Results of page number 1:"); + assertThat(output).contains("Found " + NUM_PRODUCTS_TO_TEST + " results in current page"); + assertThat(output).contains("No more available pages."); + } + + @Test + public void testSearchWithPaginationAndNoResults() throws Exception { + SearchPagination.searchWithPagination( + PROJECT_ID, PLACEMENT_ID, VISITOR_ID, "INVALID_QUERY_VALUE_FOR_TEST", 10); + String output = bout.toString(); + assertThat(output).contains("Results of page number 1:"); + assertThat(output).contains("Found 0 results in current page"); + assertThat(output).contains("No more available pages."); + } + + @Test + public void testSearchWithPagination_negativePageSize_throwsInvalidArgumentException() + throws Exception { + int pageSize = -1; + assertThrows( + InvalidArgumentException.class, + () -> + SearchPagination.searchWithPagination( + PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, pageSize)); + } +} diff --git a/retail/snippets/src/test/java/com/example/search/Utils.java b/retail/snippets/src/test/java/com/example/search/Utils.java new file mode 100644 index 00000000000..3d53ae60a89 --- /dev/null +++ b/retail/snippets/src/test/java/com/example/search/Utils.java @@ -0,0 +1,124 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +import com.google.cloud.retail.v2.BranchName; +import com.google.cloud.retail.v2.CreateProductRequest; +import com.google.cloud.retail.v2.DeleteProductRequest; +import com.google.cloud.retail.v2.PriceInfo; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.Product.Availability; +import com.google.cloud.retail.v2.Product.Type; +import com.google.cloud.retail.v2.ProductServiceClient; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchServiceClient; +import com.google.cloud.retail.v2.SearchServiceClient.SearchPagedResponse; +import com.google.cloud.retail.v2.ServingConfigName; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class Utils { + + public static List createProductsToTest( + int amount, String projectId, String productTitle) throws IOException { + List productIds = new ArrayList<>(); + for (int i = 0; i < amount; i++) { + productIds.add(UUID.randomUUID().toString()); + } + return createProducts(projectId, productIds, productTitle); + } + + public static List createProducts( + String projectId, List productIds, String productTitle) throws IOException { + BranchName branchName = BranchName.of(projectId, "global", "default_catalog", "default_branch"); + + float price = 8.0f; + float originalPrice = 12.0f; + + PriceInfo priceInfo = + PriceInfo.newBuilder() + .setPrice(price) + .setOriginalPrice(originalPrice) + .setCurrencyCode("USD") + .build(); + + Product generatedProduct = + Product.newBuilder() + .setTitle(productTitle) + .setType(Type.PRIMARY) + .addCategories("Beverages") + .addBrands("Google") + .setPriceInfo(priceInfo) + .setAvailability(Availability.IN_STOCK) + .build(); + List createdProducts = new ArrayList<>(); + try (ProductServiceClient serviceClient = ProductServiceClient.create()) { + for (String productId : productIds) { + CreateProductRequest createProductRequest = + CreateProductRequest.newBuilder() + .setProduct(generatedProduct) + .setProductId(productId) + .setParent(branchName.toString()) + .build(); + + createdProducts.add(serviceClient.createProduct(createProductRequest)); + } + } + return createdProducts; + } + + public static void deleteProducts(List products) throws IOException { + try (ProductServiceClient serviceClient = ProductServiceClient.create()) { + for (Product product : products) { + DeleteProductRequest deleteProductRequest = + DeleteProductRequest.newBuilder().setName(product.getName()).build(); + + serviceClient.deleteProduct(deleteProductRequest); + } + } + } + + public static void waitForProductsToBeReadyToTest( + String projectId, List products, String productTitle) + throws InterruptedException, IOException { + try (SearchServiceClient searchServiceClient = SearchServiceClient.create()) { + ServingConfigName servingConfigName = + ServingConfigName.of(projectId, "global", "default_catalog", "default_search"); + BranchName branchName = + BranchName.of(projectId, "global", "default_catalog", "default_branch"); + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(servingConfigName.toString()) + .setBranch(branchName.toString()) + .setVisitorId("test_visitor") + .setQuery(productTitle) + .setPageSize(products.size()) + .build(); + + for (int i = 0; i < 3; i++) { + Thread.sleep(10000); + + SearchPagedResponse response = searchServiceClient.search(searchRequest); + if (response.getPage().getResponse().getResultsCount() == products.size()) { + break; + } + } + } + } +}