From c6dc9142982ce52e704c56e7d93bda593065d072 Mon Sep 17 00:00:00 2001 From: thomasht86 Date: Wed, 3 Jul 2024 10:19:49 +0200 Subject: [PATCH] run testapp prod --- .github/workflows/deploy-with-java.yml | 57 +++++++++++++ .github/workflows/deploy_to_prod.py | 37 +++++++++ .../.gitignore | 2 + .../README.md | 52 ++++++++++++ .../ext/test-config-local.json | 5 ++ .../ext/test-config.json | 5 ++ .../pom.xml | 27 ++++++ .../src/main/application/deployment.xml | 8 ++ .../src/main/application/schemas/music.sd | 48 +++++++++++ .../src/main/application/services.xml | 23 +++++ .../vespa/example/album/ApplicationMain.java | 30 +++++++ .../album/FeedAndSearchSystemTest.java | 63 ++++++++++++++ .../vespa/example/album/StagingCommons.java | 83 +++++++++++++++++++ .../vespa/example/album/StagingSetupTest.java | 38 +++++++++ .../album/StagingVerificationTest.java | 47 +++++++++++ .../test/resources/A-Head-Full-of-Dreams.json | 15 ++++ .../Hardwired...To-Self-Destruct.json | 16 ++++ .../test/resources/Love-Is-Here-To-Stay.json | 16 ++++ 18 files changed, 572 insertions(+) create mode 100644 .github/workflows/deploy-with-java.yml create mode 100644 .github/workflows/deploy_to_prod.py create mode 100644 tests/testapps/production-deployment-with-tests-java/.gitignore create mode 100644 tests/testapps/production-deployment-with-tests-java/README.md create mode 100644 tests/testapps/production-deployment-with-tests-java/ext/test-config-local.json create mode 100644 tests/testapps/production-deployment-with-tests-java/ext/test-config.json create mode 100644 tests/testapps/production-deployment-with-tests-java/pom.xml create mode 100644 tests/testapps/production-deployment-with-tests-java/src/main/application/deployment.xml create mode 100644 tests/testapps/production-deployment-with-tests-java/src/main/application/schemas/music.sd create mode 100644 tests/testapps/production-deployment-with-tests-java/src/main/application/services.xml create mode 100644 tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/ApplicationMain.java create mode 100644 tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/FeedAndSearchSystemTest.java create mode 100644 tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingCommons.java create mode 100644 tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingSetupTest.java create mode 100644 tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingVerificationTest.java create mode 100644 tests/testapps/production-deployment-with-tests-java/src/test/resources/A-Head-Full-of-Dreams.json create mode 100644 tests/testapps/production-deployment-with-tests-java/src/test/resources/Hardwired...To-Self-Destruct.json create mode 100644 tests/testapps/production-deployment-with-tests-java/src/test/resources/Love-Is-Here-To-Stay.json diff --git a/.github/workflows/deploy-with-java.yml b/.github/workflows/deploy-with-java.yml new file mode 100644 index 00000000..f157e752 --- /dev/null +++ b/.github/workflows/deploy-with-java.yml @@ -0,0 +1,57 @@ +# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +name: pyvespa - Build and Deploy with Java - Github Action + +on: + workflow_dispatch: + push: + branches: + - thomasht86/tests-to-github-action + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Vespa uses Java 17 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: "temurin" + java-version: "17" + # Find Vespa version of current production deployment + - name: Find compile version + working-directory: tests/testapps/production-deployment-with-tests-java + run: mvn -B clean vespa:compileVersion -DapiKey="${VESPA_TEAM_VESPACLOUD_DOCSEARCH_API_KEY}" + + # Build the application package and the tester bundle + - name: Build with Maven + working-directory: tests/testapps/production-deployment-with-tests-java + run: mvn -B package -Dvespa.compile.version="$(cat target/vespa.compile.version)" + + # Upload artifacts to be used in the deploy job + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + path: target/* + name: applicationpackage + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" # caching pip dependencies + - name: Install dependencies + run: pip install pyvespa vespacli + + - name: Deploy to prod + env: + VESPA_TEAM_API_KEY: ${{ secrets.VESPA_TEAM_API_KEY }} + run: | + python .github/workflows/deploy_to_prod.py \ + --tenant vespa-team \ + --application testapp \ + --api-key $VESPA_TEAM_API_KEY \ + --application-root tests/testapps/production-deployment-with-tests-java/target/ \ + --max-wait 3600 \ + --source-url "$(git config --get remote.origin.url | sed 's+git@\(.*\):\(.*\)\.git+https://\1/\2+')/commit/$(git rev-parse HEAD)" diff --git a/.github/workflows/deploy_to_prod.py b/.github/workflows/deploy_to_prod.py new file mode 100644 index 00000000..6dea97ff --- /dev/null +++ b/.github/workflows/deploy_to_prod.py @@ -0,0 +1,37 @@ +# Command line script to deploy Vespa applications to Vespa Cloud +# Usage: vespa-deploy.py --tenant --application --api-key --application-root --max-wait 3600 --source-url + +import argparse + +from vespa.deployment import VespaCloud + + +def deploy_prod( + tenant, application, api_key, application_root, max_wait=3600, source_url=None +): + vespa_cloud = VespaCloud( + tenant=tenant, + application=application, + key_content=api_key, + ) + build_no = vespa_cloud.deploy_to_prod(application_root, source_url=source_url) + vespa_cloud.wait_for_prod_deployment(build_no, max_wait=max_wait) + + +if __name__ == "__main__": + args = argparse.ArgumentParser() + args.add_argument("--tenant", required=True, help="Vespa Cloud tenant") + args.add_argument("--application", required=True, help="Vespa Cloud application") + args.add_argument("--api-key", required=True, help="Vespa Cloud API key") + args.add_argument( + "--application-root", required=True, help="Path to the Vespa application root" + ) + args.add_argument( + "--max-wait", type=int, default=3600, help="Max wait time in seconds" + ) + args.add_argument( + "--source-url", help="Source URL (git commit URL) for the deployment" + ) + + args = args.parse_args() + deploy_prod(args.tenant, args.application, args.api_key, args.application_root) diff --git a/tests/testapps/production-deployment-with-tests-java/.gitignore b/tests/testapps/production-deployment-with-tests-java/.gitignore new file mode 100644 index 00000000..92322c4e --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/.gitignore @@ -0,0 +1,2 @@ +.idea/ +target/ diff --git a/tests/testapps/production-deployment-with-tests-java/README.md b/tests/testapps/production-deployment-with-tests-java/README.md new file mode 100644 index 00000000..ccd84757 --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/README.md @@ -0,0 +1,52 @@ + + +![Vespa Cloud logo](https://cloud.vespa.ai/assets/logos/vespa-cloud-logo-full-black.png) + +# Production Deployment with Java Tests + +A minimal Vespa Cloud application for deployment into a Production zone - with basic Java-tests. + +An application using Java test code must be deployed using the procedure for +[production deployment with components](https://cloud.vespa.ai/en/production-deployment#production-deployment-with-components) - +steps: + +``` +vespa config set target cloud +vespa config set application mytenant.myapp.myinstance +vespa auth login +mvn clean +vespa auth cert -f +mvn vespa:compileVersion -Dtenant=mytenant -Dapplication=myapp +mvn -U package -Dvespa.compile.version="$(cat target/vespa.compile.version)" +vespa prod deploy +``` + + +## Developing system and staging tests +Develop tests using an instance in the Dev zone. +Use the Console and upload `target/application.zip` built in the steps above - use "default" instance name. + + mvn test -D test.categories=system \ + -D vespa.test.config=ext/test-config.json \ + -D dataPlaneCertificateFile=data-plane-public-cert.pem \ + -D dataPlaneKeyFile=data-plane-private-key.pem + + mvn test -D test.categories=staging-setup \ + -D vespa.test.config=ext/test-config.json \ + -D dataPlaneCertificateFile=data-plane-public-cert.pem \ + -D dataPlaneKeyFile=data-plane-private-key.pem + + mvn test -D test.categories=staging \ + -D vespa.test.config=ext/test-config.json \ + -D dataPlaneCertificateFile=data-plane-public-cert.pem \ + -D dataPlaneKeyFile=data-plane-private-key.pem + + +One can also use a local instance: + + mvn test -D test.categories=system -D vespa.test.config=ext/test-config-local.json + mvn test -D test.categories=staging-setup -D vespa.test.config=ext/test-config-local.json + mvn test -D test.categories=staging -D vespa.test.config=ext/test-config-local.json + +See [Vespa Cloud Automated Deployments](https://cloud.vespa.ai/en/automated-deployments) +for an overview of production deployments. diff --git a/tests/testapps/production-deployment-with-tests-java/ext/test-config-local.json b/tests/testapps/production-deployment-with-tests-java/ext/test-config-local.json new file mode 100644 index 00000000..542f66d8 --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/ext/test-config-local.json @@ -0,0 +1,5 @@ +{ + "localEndpoints": { + "default" : "http://localhost:8080/" + } +} diff --git a/tests/testapps/production-deployment-with-tests-java/ext/test-config.json b/tests/testapps/production-deployment-with-tests-java/ext/test-config.json new file mode 100644 index 00000000..a9e38d50 --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/ext/test-config.json @@ -0,0 +1,5 @@ +{ + "localEndpoints": { + "default" : "https://default.myapp.mytenant.aws-us-east-1c.dev.z.vespa-app.cloud/" + } +} diff --git a/tests/testapps/production-deployment-with-tests-java/pom.xml b/tests/testapps/production-deployment-with-tests-java/pom.xml new file mode 100644 index 00000000..6c77becc --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/pom.xml @@ -0,0 +1,27 @@ + + + + 4.0.0 + ai.vespa.example + tests + 1.0.0 + pom + + demo + + + + com.yahoo.vespa + cloud-tenant-base + [8,9) + + + + + UTF-8 + + + diff --git a/tests/testapps/production-deployment-with-tests-java/src/main/application/deployment.xml b/tests/testapps/production-deployment-with-tests-java/src/main/application/deployment.xml new file mode 100644 index 00000000..7615ddbd --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/main/application/deployment.xml @@ -0,0 +1,8 @@ + + + + + aws-us-east-1c + + + diff --git a/tests/testapps/production-deployment-with-tests-java/src/main/application/schemas/music.sd b/tests/testapps/production-deployment-with-tests-java/src/main/application/schemas/music.sd new file mode 100644 index 00000000..bb547c93 --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/main/application/schemas/music.sd @@ -0,0 +1,48 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +# A description of a type of data, how to store and index it, and what to compute over the data elements +# +# See: +# - https://docs.vespa.ai/en/schemas.html +schema music { + + document music { + + field artist type string { + indexing: summary | index + } + + field album type string { + indexing: summary | index + } + + field year type int { + indexing: summary | attribute + } + + field category_scores type tensor(cat{}) { + indexing: summary | attribute + } + + } + + fieldset default { + fields: artist, album + } + + # Rank profiles defines what to compute over the data, and how to use the computation result to order them + # They can be selected at query time (ranking.profile=[name]), and can be everything from simple handwritten + # expressions as below to references to large machine-learned models. + # + # See + # - https://docs.vespa.ai/en/ranking.html + rank-profile rank_albums inherits default { + inputs { + query(user_profile) tensor(cat{}) + } + first-phase { + expression: sum(query(user_profile) * attribute(category_scores)) + } + } + +} diff --git a/tests/testapps/production-deployment-with-tests-java/src/main/application/services.xml b/tests/testapps/production-deployment-with-tests-java/src/main/application/services.xml new file mode 100644 index 00000000..32f7047c --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/main/application/services.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + 2 + + + + + + + + + diff --git a/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/ApplicationMain.java b/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/ApplicationMain.java new file mode 100644 index 00000000..f0985db9 --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/ApplicationMain.java @@ -0,0 +1,30 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.example.album; + +import com.yahoo.application.Application; +import com.yahoo.application.Networking; + +import java.nio.file.FileSystems; +import java.util.Objects; + +/** + * This uses the Application class to set up a container instance of this application + * in this JVM. All other aspects of the application package than a single container + * cluster is ignored. This is useful for e.g starting a container instance in your IDE + * and serving real HTTP requests for interactive debugging. + *

+ * After running main you can e.g open + * http://localhost:8080/search/?query=title:foo&tracelevel=2 + * in a browser. + */ +public class ApplicationMain { + + public static void main(String[] args) throws Exception { + try (Application app = Application.fromApplicationPackage(FileSystems.getDefault().getPath("src/main/application"), + Networking.enable)) { + Objects.requireNonNull(app); + Thread.sleep(Long.MAX_VALUE); + } + } + +} diff --git a/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/FeedAndSearchSystemTest.java b/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/FeedAndSearchSystemTest.java new file mode 100644 index 00000000..2f59a80e --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/FeedAndSearchSystemTest.java @@ -0,0 +1,63 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.example.album; + +import ai.vespa.hosted.cd.Endpoint; +import ai.vespa.hosted.cd.SystemTest; +import ai.vespa.hosted.cd.TestRuntime; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestReporter; + +import java.io.IOException; +import java.net.http.HttpResponse; +import java.util.Map; + +import static java.net.http.HttpRequest.BodyPublishers.ofString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SystemTest +class FeedAndSearchSystemTest { + + private final Endpoint endpoint = TestRuntime.get().deploymentToTest().endpoint("default"); + + @Test + void testOutput(TestReporter testReporter) { + testReporter.publishEntry("Hello from an empty test!"); + assertTrue(true, "Text from assertion for comparison"); + } + + @Test + void feedAndSearch() throws IOException { + String documentPath = "/document/v1/mynamespace/music/docid/test1"; + String deleteAll = "/document/v1/mynamespace/music/docid?selection=true&cluster=music"; + String document = """ + { + "fields": { + "artist": "Coldplay" + } + }"""; + String yql = "SELECT * FROM music WHERE artist CONTAINS 'coldplay'"; + + HttpResponse deleteResult = endpoint.send(endpoint.request(deleteAll).DELETE()); + assertEquals(200, deleteResult.statusCode()); + + // the first query needs a higher timeout than the default 500ms, to warm up the code + HttpResponse emptyResult = endpoint.send(endpoint.request("/search/", + Map.of("yql", yql, + "timeout", "10s"))); + assertEquals(200, emptyResult.statusCode()); + assertEquals(0, new ObjectMapper().readTree(emptyResult.body()) + .get("root").get("fields").get("totalCount").asLong()); + + HttpResponse feedResult = endpoint.send(endpoint.request(documentPath) + .POST(ofString(document))); + assertEquals(200, feedResult.statusCode()); + + HttpResponse searchResult = endpoint.send(endpoint.request("/search/", + Map.of("yql", yql))); + assertEquals(200, searchResult.statusCode()); + assertEquals(1, new ObjectMapper().readTree(searchResult.body()) + .get("root").get("fields").get("totalCount").asLong()); + } +} diff --git a/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingCommons.java b/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingCommons.java new file mode 100644 index 00000000..bda757b8 --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingCommons.java @@ -0,0 +1,83 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.example.album; + +import ai.vespa.hosted.cd.Endpoint; +import ai.vespa.hosted.cd.TestRuntime; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.http.HttpResponse; +import java.util.Map; +import java.util.stream.Stream; + +import static java.net.URLEncoder.encode; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.toUnmodifiableMap; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class StagingCommons { + + private static final ObjectMapper mapper = new ObjectMapper(); + + /** Returns the container endpoint to do requests against. */ + static Endpoint container() { + return TestRuntime.get().deploymentToTest().endpoint("default"); + } + + /** Returns the document path of the document with the given name. */ + static String documentPath(String documentName) { + return "/document/v1/mynamespace/music/docid/" + encode(documentName, UTF_8); + } + + /** Reads and returns the contents of the JSON test resource with the given name. */ + static byte[] readDocumentResource(String documentName) { + try { + return StagingSetupTest.class.getResourceAsStream("/" + documentName + ".json").readAllBytes(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** Returns static document ID paths and document bytes for the three static staging test documents. */ + static Map documentsByPath() { + return Stream.of("A-Head-Full-of-Dreams", + "Hardwired...To-Self-Destruct", + "Love-Is-Here-To-Stay") + .collect(toUnmodifiableMap(StagingCommons::documentPath, + StagingCommons::readDocumentResource)); + } + + /** Warm-up query matching all "music" documents — high timeout as the fresh container needs to warm up. */ + static Map warmupQueryForAllDocuments() { + return Map.of("yql", "SELECT * FROM music WHERE true", "timeout", "10s"); + } + + static Map queryForArtist() { + return Map.of("yql", "SELECT * FROM music WHERE true"); + } + + /** Verifies the static staging documents are searchable, ranked correctly, and render as expected. */ + static void verifyDocumentsAreSearchable() throws IOException { + warmup(); + + // Verify that the cluster filters and ranks documents as expected, prior to upgrade. + HttpResponse queryResponse = container().send(container().request("/search/", queryForArtist())); + assertEquals(200, queryResponse.statusCode()); + JsonNode root = mapper.readTree(queryResponse.body()).get("root"); + assertEquals(3, root.get("fields").get("totalCount").asLong()); + } + + private static void warmup() throws IOException { + // Verify that the cluster has the fed documents, and that they are searchable. + for (int i = 0; i <= 5; i++) { + HttpResponse warmUpResponse = container().send(container().request("/search/", warmupQueryForAllDocuments())); + assertEquals(200, warmUpResponse.statusCode()); + assertEquals(3, mapper.readTree(warmUpResponse.body()) + .get("root").get("fields").get("totalCount").asLong()); + } + } + +} diff --git a/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingSetupTest.java b/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingSetupTest.java new file mode 100644 index 00000000..6fee1592 --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingSetupTest.java @@ -0,0 +1,38 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.example.album; + +import ai.vespa.hosted.cd.StagingSetup; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.http.HttpResponse; +import java.util.Map; + +import static ai.vespa.example.album.StagingCommons.container; +import static ai.vespa.example.album.StagingCommons.documentsByPath; +import static ai.vespa.example.album.StagingCommons.verifyDocumentsAreSearchable; +import static java.net.http.HttpRequest.BodyPublishers.ofByteArray; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@StagingSetup +class StagingSetupTest { + + @Test + @DisplayName("Feed documents to the staging cluster, before upgrade") + void feedAndSearch() throws IOException { + // Make sure cluster is empty + String deleteAll = "/document/v1/mynamespace/music/docid"; + HttpResponse deleteResult = container().send(container().request(deleteAll, Map.of("selection", "true", "cluster", "music")).DELETE()); + assertEquals(200, deleteResult.statusCode()); + + // Feed the static staging test documents + documentsByPath().forEach((documentPath, document) -> { + assertEquals(200, container().send(container().request(documentPath).POST(ofByteArray(document))).statusCode()); + }); + + // Verify documents are searchable and rendered as expected, prior to upgrade. + verifyDocumentsAreSearchable(); + } + +} diff --git a/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingVerificationTest.java b/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingVerificationTest.java new file mode 100644 index 00000000..5ea5ef21 --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/test/java/ai/vespa/example/album/StagingVerificationTest.java @@ -0,0 +1,47 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.example.album; + +import ai.vespa.hosted.cd.StagingTest; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.http.HttpResponse; +import java.util.Iterator; +import java.util.List; + +import static ai.vespa.example.album.StagingCommons.container; +import static ai.vespa.example.album.StagingCommons.documentsByPath; +import static ai.vespa.example.album.StagingCommons.verifyDocumentsAreSearchable; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@StagingTest +class StagingVerificationTest { + + @Test + @DisplayName("Verify documents can be searched after upgrade") + void verify() throws IOException { + // Verify each of the documents fed in setup are still there, with expected data. + ObjectMapper mapper = new ObjectMapper(); + documentsByPath().forEach((documentPath, document) -> { + try { + HttpResponse documentResponse = container().send(container().request(documentPath).GET()); + assertEquals(200, documentResponse.statusCode()); + JsonNode retrieved = mapper.readTree(documentResponse.body()).get("fields"); + JsonNode expected = mapper.readTree(document).get("fields"); + for (String name : List.of("text")) + assertEquals(expected.get(name), retrieved.get(name)); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + + // Verify documents are searchable and rendered as expected, after the upgrade. + verifyDocumentsAreSearchable(); + } + +} diff --git a/tests/testapps/production-deployment-with-tests-java/src/test/resources/A-Head-Full-of-Dreams.json b/tests/testapps/production-deployment-with-tests-java/src/test/resources/A-Head-Full-of-Dreams.json new file mode 100644 index 00000000..c67b9066 --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/test/resources/A-Head-Full-of-Dreams.json @@ -0,0 +1,15 @@ +{ + "put": "id:mynamespace:music::a-head-full-of-dreams", + "fields": { + "album": "A Head Full of Dreams", + "artist": "Coldplay", + "year": 2015, + "category_scores": { + "cells": [ + { "address" : { "cat" : "pop" }, "value": 1 }, + { "address" : { "cat" : "rock" }, "value": 0.2 }, + { "address" : { "cat" : "jazz" }, "value": 0 } + ] + } + } +} diff --git a/tests/testapps/production-deployment-with-tests-java/src/test/resources/Hardwired...To-Self-Destruct.json b/tests/testapps/production-deployment-with-tests-java/src/test/resources/Hardwired...To-Self-Destruct.json new file mode 100644 index 00000000..319025e7 --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/test/resources/Hardwired...To-Self-Destruct.json @@ -0,0 +1,16 @@ +{ + "put": "id:mynamespace:music::hardwired-to-self-destruct", + "fields": { + "album": "Hardwired...To Self-Destruct", + "artist": "Metallica", + "year": 2016, + "category_scores": { + "cells": [ + { "address" : { "cat" : "pop" }, "value": 0 }, + { "address" : { "cat" : "rock" }, "value": 1 }, + { "address" : { "cat" : "jazz" }, "value": 0 } + ] + } + } +} + diff --git a/tests/testapps/production-deployment-with-tests-java/src/test/resources/Love-Is-Here-To-Stay.json b/tests/testapps/production-deployment-with-tests-java/src/test/resources/Love-Is-Here-To-Stay.json new file mode 100644 index 00000000..5d51044a --- /dev/null +++ b/tests/testapps/production-deployment-with-tests-java/src/test/resources/Love-Is-Here-To-Stay.json @@ -0,0 +1,16 @@ +{ + "put": "id:mynamespace:music::love-id-here-to-stay", + "fields": { + "album": "Love Is Here To Stay", + "artist": "Diana Krall", + "year": 2018, + "category_scores": { + "cells": [ + { "address" : { "cat" : "pop" }, "value": 0.4 }, + { "address" : { "cat" : "rock" }, "value": 0 }, + { "address" : { "cat" : "jazz" }, "value": 0.8 } + ] + } + } +} +