Skip to content
Merged
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@
<configuration>
<sources>
<source>${project.basedir}/src/it/java</source>
<source>${project.basedir}/src/it/resources</source>
</sources>
</configuration>
</execution>
Expand Down
14 changes: 8 additions & 6 deletions src/it/java/io/weaviate/containers/Container.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
public class Container {
public static final Weaviate WEAVIATE = Weaviate.createDefault();
public static final Contextionary CONTEXTIONARY = Contextionary.createDefault();
public static final Img2VecNeural IMG2VEC_NEURAL = Img2VecNeural.createDefault();

static {
startAll();
Expand All @@ -39,21 +40,23 @@ static void stopAll() {
WEAVIATE.stop();
}

public static Group compose(Weaviate weaviate, GenericContainer<?>... containers) {
return new Group(weaviate, containers);
public static ContainerGroup compose(Weaviate weaviate, GenericContainer<?>... containers) {
return new ContainerGroup(weaviate, containers);
}

public static TestRule asTestRule(Startable container) {
return new PerTestSuite(container);
};

public static class Group implements Startable {
public static class ContainerGroup implements Startable {
private final Weaviate weaviate;
private final List<GenericContainer<?>> containers;

private Group(Weaviate weaviate, GenericContainer<?>... containers) {
private ContainerGroup(Weaviate weaviate, GenericContainer<?>... containers) {
this.weaviate = weaviate;
this.containers = Arrays.asList(containers);

weaviate.dependsOn(containers);
setSharedNetwork();
}

Expand All @@ -63,8 +66,7 @@ public WeaviateClient getClient() {

@Override
public void start() {
containers.forEach(GenericContainer::start);
weaviate.start();
weaviate.start(); // testcontainers will resolve dependencies
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/it/java/io/weaviate/containers/Contextionary.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public Contextionary build() {
.withEnv("EXTENSIONS_STORAGE_ORIGIN", "http://weaviate:8080")
.withEnv("NEIGHBOR_OCCURRENCE_IGNORE_PERCENTILE", "5")
.withEnv("ENABLE_COMPOUND_SPLITTING", "'false'");
container.withCreateContainerCmdModifier(cmd -> cmd.withHostName("contextionary"));
container.withCreateContainerCmdModifier(cmd -> cmd.withHostName(HOST_NAME));
return container;
}
}
Expand Down
38 changes: 38 additions & 0 deletions src/it/java/io/weaviate/containers/Img2VecNeural.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.weaviate.containers;

import org.testcontainers.containers.GenericContainer;

public class Img2VecNeural extends GenericContainer<Img2VecNeural> {
public static final String DOCKER_IMAGE = "cr.weaviate.io/semitechnologies/img2vec-pytorch";
public static final String VERSION = "resnet50";

public static final String MODULE = "img2vec-neural";
public static final String HOST_NAME = MODULE;
public static final String URL = HOST_NAME + ":8080";

static Img2VecNeural createDefault() {
return new Builder().build();
}

static Img2VecNeural.Builder custom() {
return new Builder();
}

public static class Builder {
private String versionTag;

public Builder() {
this.versionTag = VERSION;
}

public Img2VecNeural build() {
var container = new Img2VecNeural(DOCKER_IMAGE + ":" + versionTag);
container.withCreateContainerCmdModifier(cmd -> cmd.withHostName(HOST_NAME));
return container;
}
}

public Img2VecNeural(String image) {
super(image);
}
}
40 changes: 23 additions & 17 deletions src/it/java/io/weaviate/containers/Weaviate.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.weaviate.containers;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.testcontainers.weaviate.WeaviateContainer;
Expand All @@ -10,7 +13,7 @@
import io.weaviate.client6.WeaviateClient;

public class Weaviate extends WeaviateContainer {
private static WeaviateClient clientInstance;
private WeaviateClient clientInstance;

public static final String VERSION = "1.29.0";
public static final String DOCKER_IMAGE = "semitechnologies/weaviate";
Expand Down Expand Up @@ -42,14 +45,14 @@ public static Weaviate.Builder custom() {

public static class Builder {
private String versionTag;
private Set<String> enableModules;
private Set<String> enableModules = new HashSet<>();
private String defaultVectorizerModule;
private String contextionaryUrl;
private boolean telemetry;

private Map<String, String> environment = new HashMap<>();

public Builder() {
this.versionTag = VERSION;
this.enableModules = new HashSet<>();
this.telemetry = false;
}

Expand All @@ -58,43 +61,46 @@ public Builder withVersion(String version) {
return this;
}

public Builder addModule(String module) {
enableModules.add(module);
public Builder addModules(String... modules) {
enableModules.addAll(Arrays.asList(modules));
return this;
}

public Builder withDefaultVectorizer(String module) {
addModule(module);
defaultVectorizerModule = module;
addModules(module);
environment.put("DEFAULT_VECTORIZER_MODULE", module);
return this;
}

public Builder withContextionaryUrl(String url) {
contextionaryUrl = url;
addModules(Contextionary.MODULE);
environment.put("CONTEXTIONARY_URL", url);
return this;
}

public Builder withImageInference(String url, String module) {
addModules(module);
environment.put("IMAGE_INFERENCE_API", "http://" + url);
return this;
}

public Builder enableTelemetry() {
telemetry = true;
public Builder enableTelemetry(boolean enable) {
telemetry = enable;
return this;
}

public Weaviate build() {
var c = new Weaviate(DOCKER_IMAGE + ":" + versionTag);

if (!enableModules.isEmpty()) {
c.withEnv("ENABLE_API_BASED_MODULES", "'true'");
c.withEnv("ENABLE_MODULES", String.join(",", enableModules));
}
if (defaultVectorizerModule != null) {
c.withEnv("DEFAULT_VECTORIZER_MODULE", defaultVectorizerModule);
}
if (contextionaryUrl != null) {
c.withEnv("CONTEXTIONARY_URL", contextionaryUrl);
}
if (!telemetry) {
c.withEnv("DISABLE_TELEMETRY", "true");
}

environment.forEach((name, value) -> c.withEnv(name, value));
c.withCreateContainerCmdModifier(cmd -> cmd.withHostName("weaviate"));
return c;
}
Expand Down
1 change: 0 additions & 1 deletion src/it/java/io/weaviate/integration/CollectionsITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ public void testCrossReferences() throws IOException {

// Assert: Things --ownedBy-> Owners
Assertions.assertThat(things.config.get())
// Assertions.assertThat(client.collections.getConfig(nsOwners))
.as("after create Things").get()
.satisfies(c -> {
Assertions.assertThat(c.references())
Expand Down
24 changes: 24 additions & 0 deletions src/it/java/io/weaviate/integration/DataITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.weaviate.client6.v1.collections.VectorIndex.IndexingStrategy;
import io.weaviate.client6.v1.collections.Vectorizer;
import io.weaviate.client6.v1.collections.object.Vectors;
import io.weaviate.client6.v1.collections.object.WeaviateObject;
import io.weaviate.containers.Container;

public class DataITest extends ConcurrentTest {
Expand Down Expand Up @@ -62,6 +63,29 @@ public void testCreateGetDelete() throws IOException {
Assertions.assertThat(object).isEmpty().as("object not exists after deletion");
}

@Test
public void testBlobData() throws IOException {
var nsCats = ns("Cats");

client.collections.create(nsCats,
collection -> collection.properties(
Property.text("breed"),
Property.blob("img")));

var cats = client.collections.use(nsCats);
var ragdollPng = EncodedMedia.IMAGE;
var ragdoll = cats.data.insert(Map.of(
"breed", "ragdoll",
"img", ragdollPng));

var got = cats.data.get(ragdoll.metadata().id(),
cat -> cat.returnProperties("img"));

Assertions.assertThat(got).get()
.extracting(WeaviateObject::properties, InstanceOfAssertFactories.MAP)
.extractingByKey("img").isEqualTo(ragdollPng);
}

private static void createTestCollections() throws IOException {
var awardsGrammy = unique("Grammy");
client.collections.create(awardsGrammy);
Expand Down
6 changes: 6 additions & 0 deletions src/it/java/io/weaviate/integration/EncodedMedia.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.weaviate.integration;

class EncodedMedia {
public static final String IMAGE = "/9j/4AAQSkZJRgABAQAAkACQAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAACQAAAAAQAAAJAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAnKgAwAEAAAAAQAAAbsAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJTQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/AABEIAbsCcgMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2wBDABwcHBwcHDAcHDBEMDAwRFxEREREXHRcXFxcXHSMdHR0dHR0jIyMjIyMjIyoqKioqKjExMTExNzc3Nzc3Nzc3Nz/2wBDASIkJDg0OGA0NGDmnICc5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ub/3QAEACj/2gAMAwEAAhEDEQA/AOkooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9DpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/R6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/0ukooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9PpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/U6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/1ekooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9bpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/X6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoophdR3pN23AfRUfmj0pRIDU+0j3HYfRRRViCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooJA5NABRURlXOBzR5hPas3Viuo7EtFM3+tPBzVKSewgoooqgCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//Q6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKQkDk0ALTWdV61C0pPC1HWMqvYdhzOzewpAKMU6uaUmyhRS4BoBFPAqUmwGqxBwelTVXPWplORXRRn9liaHUUUV0khRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAASAMmqruWNSSN2qsa5a9S3uoqKJBUgquCRT9xrl5irE+aX6VEGp4NaRmKw8N60+o6OR0rojV7isSUU0MDTq3TT2JCiiimAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH/9HpKKKKACiiigAooooAKKKKACiiigAooooAKKKKACimNIq1XZ2br0rOVRIdiZpQOF5qAkscmiiueU29x2ClxRS1k2MKQmjNFZtjFXrVioV61N2roo7EsgPWpE61F3qRfvCsoP3kNk1FFFekQFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUhOBmlpr/doYFZzUZqRutRmvMqfEzRCU4U2nCsxjqXOKSigCQN608GoaASKpTsKxP1oBI61GG9akzW8agrDwQaWo8elKG7GumNTuTYfRR1orUQUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/9LpKKKKACiiigAooooAKKKKACiiigAoozjk1A8vZfzqZSS3AlZ1XrVdpGb2FM+tFc8qjZVgpaSlrFsYtFJmiochi5pKSlrNsYUtJSimgJFqQ9KjWnt0rqhsQyv3qVeoqIdalXqKwj8SGyeiiivTICiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKa/3adTX+7SewFZutRmnt1phrzanxMtCU6m06oGLS0lFIYtFFFAC0oJFNooAlDZp1Q0oYirjMViXp0p4b1qMHNL1rohUtsS0S0VGCRTwQa6YzTFYWiiirEFFFFABRRRQAUUUUAFFFFABRRRQB//T6SiiigAooooAKKKKACiiigAoJwMmioJmx8tTKXKrgMdyx9qjpM0VxuV9ShaWkorNyGOpM0lFQ2MWiiipGFFFFAC0opKUVSESLTm+7TVpz9K6V8JJXHWpF+8KiHWpV+8KwhuhlmiiivTICiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKa/3adTX+6aT2AqN1plPbrTK82puy0FLSUtQMWlpKKAFooopDFopKKAFooooAWnBvWm0UJtATA0vuKhBxU1dEJXJY8HNLUIJDVNXXTnzIlhRRRWggooooAKKKKACiiigAooooA//U6SiiigAooooAKKKKACiiigAqlKcuau1Qb7xrnxD0SGhtFJS1xModRSUtIBaKSloGFLSUUALRRRQAtKKSlqkIkWlfpSLSv0rdfCIrjrUi/eFRCpV+8KxhuhlqiiivTICiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKa/3TTqY/3TSewFRutMpzdabXmz3ZYUtJS1IC0tJS0hhRRRQAtFJS0ALRSUtAC0tJS07AFSKcio6cnWrhuDFbrUw6VC/WpV6V00n7zRLHUUUV0khRRRQAUUUUAF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WkFFFFaDCiiigAooooAKKKKACiiigAooooAKKKKACiiigD//0OkooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACopIUk68H1qWik1cDIlt3j7cVWIroCM8Gqc1qG+ZOD6VjKl2IcTKoqRo2U4IphFYNEBSim0A0APYZFZs6kPn1rSBzUM0e8e9aU58srgZtFOKMO1JXemAoGakVaavJwBk1qW9q74yMUNgkV4oWc9K2YLVYxl+T6VPFCkQ46+tTVm3c0SCiiikUFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAV57aOdeeD61hTW7wthxXS0x0SRdjjIpp2E1c5NkzyKiKkVr3Nm8Pzp8y/yrOODxWikjNogoNPKntSBCTihyRJJCuWz6VcpiJsXFSVw1JXYC0tJS1iMKUClAqVUzTSHYYBmpkiLHirEduTyauKoUYFbRplqJDHAF5arFFFbpJbFBRRRTAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//R6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigBjxq45qhJasORyPatKiplBMTRhGMimYrdaNH+8Kha1Q9DisXSfQlxMjFO5rR+yD1oFoPWp9mxcplsgbtTfs241tC1QdTmpljRPuitIwkh8pnQWWME8CtJVCDaKdRWqRSVgooopjCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKz7ixR8tHwfTtWhRSaBo5trd1OGFATb2romRX+8M1A1qh6HFZyjLuQ4GNg0uK1DZ+/6Ugs/esvZsXKZwU08Ia0har3NTLDGvQVSpMfKUI4GbtV5IVXrzUtFaxgkUkFFFFWMKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//S6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/2Q==";

}
Loading