diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..0da7299
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+
+jobs:
+ unit-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Java 11
+ uses: actions/setup-java@v4
+ with:
+ java-version: '11'
+ distribution: temurin
+ cache: maven
+
+ - name: Build and unit test
+ run: mvn --batch-mode test
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..6b77003
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,52 @@
+name: Release
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ environment: MVN
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 11
+ uses: actions/setup-java@v4
+ with:
+ java-version: '11'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Extract and validate version from tag
+ id: version
+ run: |
+ TAG="${{ github.event.release.tag_name }}"
+ VERSION="${TAG#v}"
+ if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
+ echo "Error: '$VERSION' is not valid semver (expected X.Y.Z)"
+ exit 1
+ fi
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+
+ - name: Set version in pom.xml
+ run: mvn versions:set -DnewVersion=${{ steps.version.outputs.version }} -DgenerateBackupPoms=false
+
+ - name: Build release JAR
+ run: mvn package -DskipTests
+
+ - name: Upload JAR to release
+ run: |
+ gh release upload "${{ github.event.release.tag_name }}" \
+ "target/ndex-java-client-${{ steps.version.outputs.version }}.jar" \
+ --clobber
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Deploy to Maven repository
+ run: mvn deploy -DskipTests --settings .mvn/settings.xml
+ env:
+ REPO_USER: ${{ secrets.REPO_USER }}
+ REPO_PWD: ${{ secrets.REPO_PWD }}
diff --git a/.mvn/settings.xml b/.mvn/settings.xml
new file mode 100644
index 0000000..246530a
--- /dev/null
+++ b/.mvn/settings.xml
@@ -0,0 +1,14 @@
+
+
+
+ cytoscape_releases
+ ${env.REPO_USER}
+ ${env.REPO_PWD}
+
+
+ cytoscape_snapshots
+ ${env.REPO_USER}
+ ${env.REPO_PWD}
+
+
+
diff --git a/.travis.settings.xml b/.travis.settings.xml
deleted file mode 100644
index e817c64..0000000
--- a/.travis.settings.xml
+++ /dev/null
@@ -1,142 +0,0 @@
-
-
-
-
- releases
- ${env.NEXUS_USERNAME}
- ${env.NEXUS_PASSWORD}
-
-
- snapshots
- ${env.NEXUS_USERNAME}
- ${env.NEXUS_PASSWORD}
-
-
- thirdparty
- ${env.NEXUS_USERNAME}
- ${env.NEXUS_PASSWORD}
-
-
-
-
- standard-with-extra-repos
-
- true
-
-
-
- cytoscape_snapshots
-
- true
-
-
- false
-
- Cytoscape Snapshots
- ${env.NEXUS_SNAPSHOT_URL}
-
-
- cytoscape_releases
-
- false
-
-
- true
-
- Cytoscape Releases
- http://code.cytoscape.org/nexus/content/repositories/releases/
-
-
- cytoscape_thirdparty
-
- false
-
-
- true
-
- Cytoscape Third Party
- http://code.cytoscape.org/nexus/content/repositories/thirdparty/
-
-
- central
- Central Repository
- http://repo.maven.apache.org/maven2
-
- true
-
-
- false
-
-
-
- sonatype
- OSS Sonatype repo (releases)
-
- true
- always
- warn
-
-
- false
- never
- fail
-
- https://oss.sonatype.org/content/repositories/releases/
-
-
- sonatype-snapshots
- OSS Sonatype repo (snapshots)
-
- false
- always
- warn
-
-
- true
- never
- fail
-
- https://oss.sonatype.org/content/repositories/snapshots/
-
-
- sonatype-apache
- Apache repo (releases)
-
- true
- always
- warn
-
-
- false
- never
- fail
-
- https://repository.apache.org/content/repositories/releases/
-
-
- apache-snapshots
- ASF repo (snapshots)
-
- false
- never
- warn
-
-
- true
- always
- fail
-
- https://repository.apache.org/snapshots/
-
-
-
-
-
-
- google-maven-central
- GCS Maven Central mirror
- https://maven-central.storage-download.googleapis.com/repos/central/data/
- central
-
-
-
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 55ea7d1..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-language: java
-
-sudo: false
-
-jdk:
- - openjdk11
-
-branches:
- only:
- master
-
-before_script:
- - cp src/test/resources/ndex-server.properties.sample src/test/resources/ndex-server.properties
- - sed -i 's/PLACEHOLDER_HOST/'"$SERVER_HOST"'/g' src/test/resources/ndex-server.properties
- - sed -i 's/PLACEHOLDER_USERNAME/'"$SERVER_USERNAME"'/g' src/test/resources/ndex-server.properties
- - sed -i 's/PLACEHOLDER_PASSWORD/'"$SERVER_PASSWORD"'/g' src/test/resources/ndex-server.properties
-
-# deploy:
-# provider: script
-# script: "cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy"
-# skip_cleanup: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..5d5a393
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,21 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [3.0.1] - 2026-09-03
+
+### Added
+
+- **NDEx v3 folder support** — `getMyFolders(int limit)` lists the signed-in user's folders via `GET /v3/files/folders`, and `moveNetworks(MoveNetworksRequest)` moves networks into a folder via `POST /v3/batch/networks/move`.
+- **NDEx v3 file search** — `searchFiles(SimpleFileQuery, FileVisibilityType, int start, int size)` queries `POST /v3/search/files`, supporting an optional `PUBLIC`/`PRIVATE` visibility filter and paging, and returning a `FileSearchResult`.
+- **Visibility and folder on CX2 network create** — `createCX2Network(InputStream, VisibilityType, UUID folderId)` adds the `visibility` and `folderId` query parameters to `POST /v3/networks`, so a network can be created directly into a folder with its visibility set. A null value omits its parameter.
+- **Visibility on CX2 network update** — `updateCX2Network(UUID, InputStream, VisibilityType)` adds the `visibility` query parameter to `PUT /v3/networks/{networkid}`. A null value omits it.
+
+The pre-existing single-argument `createCX2Network(InputStream)` and `updateCX2Network(UUID, InputStream)` now delegate to the new overloads with null arguments and produce exactly the same unqueried routes as before, so existing callers are unaffected.
+
+### Removed
+
+- **Travis CI configuration** — deleted `.travis.yml` and `.travis.settings.xml` and dropped the Travis build-status badge from `README.md`. Replaced by GitHub Actions `ci.yml` and `release.yml` workflows, the latter deploying to the NRNB Nexus repository on a published GitHub release.
diff --git a/Makefile b/Makefile
index 7c76317..52f9425 100644
--- a/Makefile
+++ b/Makefile
@@ -43,5 +43,10 @@ coverage: ## check code coverage with jacoco
install: clean ## install the package to local repo
mvn install
+deploy: ## deploy to nrnb nexus, requires VERSION=x.y.z[-SNAPSHOT]
+ @test -n "$(VERSION)" || (echo "VERSION is required, e.g. make deploy VERSION=3.0.1-SNAPSHOT" && exit 1)
+ mvn versions:set -DnewVersion=$(VERSION) -DgenerateBackupPoms=false
+ mvn deploy
+
updateversion: ## updates version in pom.xml via maven command
mvn versions:set
diff --git a/README.md b/README.md
index e3d3182..dbaa978 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,3 @@
# ndex-java-client
-[](https://app.travis-ci.com/ndexbio/ndex-java-client)
-
Java client library for accessing NDEx REST server
diff --git a/pom.xml b/pom.xml
index 93b3490..5ba99a5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
org.ndexbio.client
ndex-java-client
- 3.0.0
+ 3.0.1
NDEx Java REST Client
REST Client for in applications that access NDEx
2013
diff --git a/src/main/java/org/ndexbio/rest/client/NdexRestClient.java b/src/main/java/org/ndexbio/rest/client/NdexRestClient.java
index 40c197f..5d005f7 100644
--- a/src/main/java/org/ndexbio/rest/client/NdexRestClient.java
+++ b/src/main/java/org/ndexbio/rest/client/NdexRestClient.java
@@ -595,6 +595,27 @@ private HttpURLConnection putReturningConnection(final String route,
*/
+ /**
+ * POSTs a JSON body to a route that returns no content of interest, validating the
+ * response status the same way {@link #putNdexObject(String, JsonNode)} does.
+ */
+ protected void postNdexObjectNoContent(
+ final String route,
+ final JsonNode postData)
+ throws IllegalStateException, Exception {
+ HttpURLConnection con = null;
+ try {
+ con = postReturningConnection(route, postData);
+ if ( con.getResponseCode() != HttpURLConnection.HTTP_NO_CONTENT &&
+ con.getResponseCode() != HttpURLConnection.HTTP_OK &&
+ con.getResponseCode() != HttpURLConnection.HTTP_CREATED) {
+ processNdexSpecificException(con.getErrorStream(), con.getResponseCode(), new ObjectMapper());
+ }
+ } finally {
+ if ( con != null) con.disconnect();
+ }
+ }
+
protected T postNdexObject(
final String route,
final JsonNode postData,
diff --git a/src/main/java/org/ndexbio/rest/client/NdexRestClientModelAccessLayer.java b/src/main/java/org/ndexbio/rest/client/NdexRestClientModelAccessLayer.java
index 02088bd..da7bb1c 100644
--- a/src/main/java/org/ndexbio/rest/client/NdexRestClientModelAccessLayer.java
+++ b/src/main/java/org/ndexbio/rest/client/NdexRestClientModelAccessLayer.java
@@ -47,6 +47,11 @@
import org.ndexbio.model.exceptions.BadRequestException;
import org.ndexbio.model.exceptions.NdexException;
import org.ndexbio.model.object.CXSimplePathQuery;
+import org.ndexbio.model.object.FileSearchResult;
+import org.ndexbio.model.object.FileVisibilityType;
+import org.ndexbio.model.object.MoveNetworksRequest;
+import org.ndexbio.model.object.NdexFolder;
+import org.ndexbio.model.object.SimpleFileQuery;
import org.ndexbio.model.object.Group;
import org.ndexbio.model.object.NdexPropertyValuePair;
import org.ndexbio.model.object.NdexStatus;
@@ -58,6 +63,7 @@
import org.ndexbio.model.object.Status;
import org.ndexbio.model.object.Task;
import org.ndexbio.model.object.User;
+import org.ndexbio.model.object.network.VisibilityType;
import org.ndexbio.model.object.network.NetworkSummary;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -699,12 +705,85 @@ public void setNetworkSystemProperty(UUID networkId, Map properti
}
+ /**
+ * Lists the folders owned by the signed-in user.
+ *
+ * @param limit maximum number of folders to return
+ * @return the user's folders, newest-first as ordered by the server
+ */
+ public List getMyFolders(int limit)
+ throws JsonProcessingException, IOException, NdexException {
+ final String route = NdexApiVersion.v3 + "/files/folders?limit=" + limit;
+ return ndexRestClient.getNdexObjectList(route, "", NdexFolder.class);
+ }
+
+ /**
+ * Searches files (networks, folders and shortcuts) visible to the caller.
+ *
+ * @param query the search criteria; a null query searches with no criteria
+ * @param visibility restricts results to this visibility, or null for no restriction
+ * @param start index of the first result to return
+ * @param size maximum number of results to return
+ * @return the matching files together with the total number found
+ */
+ public FileSearchResult searchFiles(SimpleFileQuery query, FileVisibilityType visibility, int start, int size)
+ throws JsonProcessingException, IOException, NdexException {
+ final String route = NdexApiVersion.v3 + "/search/files?start=" + start + "&size=" + size
+ + (visibility == null ? "" : "&visibility=" + visibility);
+ JsonNode postData = objectMapper.valueToTree(query == null ? new SimpleFileQuery() : query);
+ return ndexRestClient.postNdexObject(route, postData, FileSearchResult.class);
+ }
+
+ /**
+ * Moves networks into a target folder.
+ *
+ * @param request the networks to move and the folder to move them into
+ */
+ public void moveNetworks(MoveNetworksRequest request)
+ throws IllegalStateException, Exception {
+ final String route = NdexApiVersion.v3 + "/batch/networks/move";
+ ndexRestClient.postNdexObjectNoContent(route, objectMapper.valueToTree(request));
+ }
+
public UUID createCXNetwork (InputStream input) throws IllegalStateException, Exception {
return createNetwork(input, NdexApiVersion.v2 + "/network", txtAcceptJsonContentRequestProperties).getUuid();
}
public UUID createCX2Network (InputStream input) throws IllegalStateException, Exception {
- return createNetwork(input, NdexApiVersion.v3 + "/networks", jsonAcceptContentRequestProperties).getUuid();
+ return createCX2Network(input, null, null);
+ }
+
+ /**
+ * Creates a CX2 network on the server, optionally setting its visibility and placing it in a folder.
+ *
+ * @param input the CX2 network stream
+ * @param visibility visibility to apply on creation, or null to leave the server default
+ * @param folderId UUID of the folder to create the network in, or null for the user's root
+ * @return the UUID of the newly created network
+ */
+ public UUID createCX2Network (InputStream input, VisibilityType visibility, UUID folderId)
+ throws IllegalStateException, Exception {
+ final String route = NdexApiVersion.v3 + "/networks"
+ + buildQuery("visibility", visibility, "folderId", folderId);
+ return createNetwork(input, route, jsonAcceptContentRequestProperties).getUuid();
+ }
+
+ /**
+ * Builds a query string from alternating name/value pairs, skipping any pair whose value is null.
+ * Returns the empty string when no value survives.
+ */
+ static String buildQuery(Object... nameValuePairs) {
+ if (nameValuePairs.length % 2 != 0)
+ throw new IllegalArgumentException("expected alternating name/value pairs");
+ StringBuilder query = new StringBuilder();
+ for (int i = 0; i < nameValuePairs.length; i += 2) {
+ Object value = nameValuePairs[i + 1];
+ if (value == null)
+ continue;
+ query.append(query.length() == 0 ? "?" : "&")
+ .append(nameValuePairs[i]).append("=").append(value);
+ }
+ return query.toString();
}
private NdexObjectUpdateStatus createNetwork(InputStream input, final String route,
@@ -786,11 +865,28 @@ public void updateCXNetwork (UUID networkUUID, InputStream input) throws Illegal
}
public void updateCX2Network (UUID networkUUID, InputStream input) throws IllegalStateException, Exception {
- updateNetwork (networkUUID, input,NdexApiVersion.v3 + "/networks");
- }
+ updateCX2Network (networkUUID, input, null);
+ }
+
+ /**
+ * Replaces a CX2 network on the server, optionally changing its visibility.
+ *
+ * @param networkUUID the network to replace
+ * @param input the CX2 network stream
+ * @param visibility visibility to apply, or null to leave it unchanged
+ */
+ public void updateCX2Network (UUID networkUUID, InputStream input, VisibilityType visibility)
+ throws IllegalStateException, Exception {
+ updateNetwork (networkUUID, input, NdexApiVersion.v3 + "/networks",
+ buildQuery("visibility", visibility));
+ }
private void updateNetwork(UUID networkUUID, InputStream input, final String route) throws IllegalStateException, Exception {
- HttpURLConnection con = ndexRestClient.createReturningConnection(route + "/" + networkUUID.toString(),
+ updateNetwork(networkUUID, input, route, "");
+ }
+
+ private void updateNetwork(UUID networkUUID, InputStream input, final String route, final String query) throws IllegalStateException, Exception {
+ HttpURLConnection con = ndexRestClient.createReturningConnection(route + "/" + networkUUID.toString() + query,
input, "PUT",
jsonAcceptContentRequestProperties);
if (con.getResponseCode() != HttpURLConnection.HTTP_NO_CONTENT){
diff --git a/src/test/java/org/ndexbio/rest/client/NdexRestClientModelAccessLayerV3Test.java b/src/test/java/org/ndexbio/rest/client/NdexRestClientModelAccessLayerV3Test.java
new file mode 100644
index 0000000..d71dd8b
--- /dev/null
+++ b/src/test/java/org/ndexbio/rest/client/NdexRestClientModelAccessLayerV3Test.java
@@ -0,0 +1,278 @@
+package org.ndexbio.rest.client;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.mock;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.Test;
+import org.ndexbio.model.object.FileSearchResult;
+import org.ndexbio.model.object.FileVisibilityType;
+import org.ndexbio.model.object.MoveNetworksRequest;
+import org.ndexbio.model.object.NdexFolder;
+import org.ndexbio.model.object.SimpleFileQuery;
+import org.ndexbio.model.object.network.VisibilityType;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Covers the NDEx v3 folder, file-search and CX2 network methods, asserting the routes and
+ * query strings they build and the objects they map back.
+ */
+public class NdexRestClientModelAccessLayerV3Test {
+
+ private static final UUID NETWORK_ID = UUID.fromString("e2ae10b4-dfba-4a15-bdb1-91c2257e12ac");
+ private static final UUID FOLDER_ID = UUID.fromString("11111111-2222-3333-4444-555555555555");
+
+ // ---------- buildQuery ----------
+
+ @Test
+ public void buildQueryOmitsNullValues() {
+ assertEquals("", NdexRestClientModelAccessLayer.buildQuery("visibility", null, "folderId", null));
+ assertEquals("?visibility=PUBLIC",
+ NdexRestClientModelAccessLayer.buildQuery("visibility", VisibilityType.PUBLIC, "folderId", null));
+ assertEquals("?folderId=" + FOLDER_ID,
+ NdexRestClientModelAccessLayer.buildQuery("visibility", null, "folderId", FOLDER_ID));
+ assertEquals("?visibility=PRIVATE&folderId=" + FOLDER_ID,
+ NdexRestClientModelAccessLayer.buildQuery("visibility", VisibilityType.PRIVATE, "folderId", FOLDER_ID));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void buildQueryRejectsOddArgumentCount() {
+ NdexRestClientModelAccessLayer.buildQuery("visibility");
+ }
+
+ // ---------- getMyFolders ----------
+
+ @Test
+ public void getMyFoldersUsesV3RouteWithLimit() throws Exception {
+ NdexRestClient client = mock(NdexRestClient.class);
+ NdexFolder folder = new NdexFolder();
+ folder.setName("My Project");
+ List folders = Collections.singletonList(folder);
+ expect(client.getNdexObjectList("v3/files/folders?limit=50", "", NdexFolder.class)).andReturn(folders);
+ replay(client);
+
+ List result = new NdexRestClientModelAccessLayer(client).getMyFolders(50);
+
+ assertSame(folders, result);
+ assertEquals("My Project", result.get(0).getName());
+ verify(client);
+ }
+
+ // ---------- searchFiles ----------
+
+ @Test
+ public void searchFilesOmitsVisibilityWhenNull() throws Exception {
+ NdexRestClient client = mock(NdexRestClient.class);
+ FileSearchResult expected = new FileSearchResult(0L, 0L, Collections.emptyList());
+ expect(client.postNdexObject(eq("v3/search/files?start=0&size=100"), anyObject(JsonNode.class),
+ eq(FileSearchResult.class))).andReturn(expected);
+ replay(client);
+
+ FileSearchResult result = new NdexRestClientModelAccessLayer(client).searchFiles(null, null, 0, 100);
+
+ assertSame(expected, result);
+ verify(client);
+ }
+
+ @Test
+ public void searchFilesAppendsVisibilityAndSerializesQuery() throws Exception {
+ NdexRestClient client = mock(NdexRestClient.class);
+ Capture body = EasyMock.newCapture();
+ FileSearchResult expected = new FileSearchResult(3L, 10L, Collections.emptyList());
+ expect(client.postNdexObject(eq("v3/search/files?start=10&size=25&visibility=PRIVATE"),
+ EasyMock.capture(body), eq(FileSearchResult.class))).andReturn(expected);
+ replay(client);
+
+ SimpleFileQuery query = new SimpleFileQuery();
+ query.setSearchString("cancer signaling");
+ query.setAccountName("alice");
+ FileSearchResult result = new NdexRestClientModelAccessLayer(client)
+ .searchFiles(query, FileVisibilityType.PRIVATE, 10, 25);
+
+ assertEquals(3L, result.getNumFound());
+ assertEquals("cancer signaling", body.getValue().get("searchString").asText());
+ assertEquals("alice", body.getValue().get("accountName").asText());
+ verify(client);
+ }
+
+ @Test
+ public void searchFilesAcceptsPublicVisibility() throws Exception {
+ NdexRestClient client = mock(NdexRestClient.class);
+ expect(client.postNdexObject(eq("v3/search/files?start=0&size=5&visibility=PUBLIC"),
+ anyObject(JsonNode.class), eq(FileSearchResult.class)))
+ .andReturn(new FileSearchResult(0L, 0L, Collections.emptyList()));
+ replay(client);
+
+ new NdexRestClientModelAccessLayer(client).searchFiles(new SimpleFileQuery(), FileVisibilityType.PUBLIC, 0, 5);
+
+ verify(client);
+ }
+
+ // ---------- moveNetworks ----------
+
+ @Test
+ public void moveNetworksPostsBatchRoute() throws Exception {
+ NdexRestClient client = mock(NdexRestClient.class);
+ Capture body = EasyMock.newCapture();
+ client.postNdexObjectNoContent(eq("v3/batch/networks/move"), EasyMock.capture(body));
+ expectLastCall();
+ replay(client);
+
+ MoveNetworksRequest request = new MoveNetworksRequest();
+ request.setTargetFolder(FOLDER_ID);
+ request.setNetworks(Arrays.asList(NETWORK_ID));
+ new NdexRestClientModelAccessLayer(client).moveNetworks(request);
+
+ assertEquals(FOLDER_ID.toString(), body.getValue().get("targetFolder").asText());
+ assertEquals(NETWORK_ID.toString(), body.getValue().get("networks").get(0).asText());
+ verify(client);
+ }
+
+ // ---------- createCX2Network ----------
+
+ @Test
+ public void createCX2NetworkAppliesVisibilityAndFolder() throws Exception {
+ assertEquals("v3/networks?visibility=PUBLIC&folderId=" + FOLDER_ID,
+ captureCreateRoute(VisibilityType.PUBLIC, FOLDER_ID));
+ }
+
+ @Test
+ public void createCX2NetworkAppliesVisibilityOnly() throws Exception {
+ assertEquals("v3/networks?visibility=PRIVATE", captureCreateRoute(VisibilityType.PRIVATE, null));
+ }
+
+ @Test
+ public void createCX2NetworkAppliesFolderOnly() throws Exception {
+ assertEquals("v3/networks?folderId=" + FOLDER_ID, captureCreateRoute(null, FOLDER_ID));
+ }
+
+ @Test
+ public void createCX2NetworkWithNeitherSendsNoQuery() throws Exception {
+ assertEquals("v3/networks", captureCreateRoute(null, null));
+ }
+
+ @Test
+ public void createCX2NetworkSingleArgOverloadIsUnqueried() throws Exception {
+ NdexRestClient client = mock(NdexRestClient.class);
+ Capture route = EasyMock.newCapture();
+ expect(client.createReturningConnection(EasyMock.capture(route), anyObject(InputStream.class), eq("POST"),
+ anyObject())).andReturn(createdConnection(NETWORK_ID));
+ replay(client);
+
+ UUID id = new NdexRestClientModelAccessLayer(client).createCX2Network(cx2Stream());
+
+ assertEquals("v3/networks", route.getValue());
+ assertEquals(NETWORK_ID, id);
+ verify(client);
+ }
+
+ private String captureCreateRoute(VisibilityType visibility, UUID folderId) throws Exception {
+ NdexRestClient client = mock(NdexRestClient.class);
+ Capture route = EasyMock.newCapture();
+ expect(client.createReturningConnection(EasyMock.capture(route), anyObject(InputStream.class), eq("POST"),
+ anyObject())).andReturn(createdConnection(NETWORK_ID));
+ replay(client);
+
+ UUID id = new NdexRestClientModelAccessLayer(client).createCX2Network(cx2Stream(), visibility, folderId);
+
+ assertEquals(NETWORK_ID, id);
+ verify(client);
+ return route.getValue();
+ }
+
+ // ---------- updateCX2Network ----------
+
+ @Test
+ public void updateCX2NetworkAppendsVisibility() throws Exception {
+ assertEquals("v3/networks/" + NETWORK_ID + "?visibility=PUBLIC",
+ captureUpdateRoute(VisibilityType.PUBLIC));
+ }
+
+ @Test
+ public void updateCX2NetworkOmitsVisibilityWhenNull() throws Exception {
+ assertEquals("v3/networks/" + NETWORK_ID, captureUpdateRoute(null));
+ }
+
+ @Test
+ public void updateCX2NetworkSingleArgOverloadIsUnqueried() throws Exception {
+ NdexRestClient client = mock(NdexRestClient.class);
+ Capture route = EasyMock.newCapture();
+ expect(client.createReturningConnection(EasyMock.capture(route), anyObject(InputStream.class), eq("PUT"),
+ anyObject())).andReturn(noContentConnection());
+ replay(client);
+
+ new NdexRestClientModelAccessLayer(client).updateCX2Network(NETWORK_ID, cx2Stream());
+
+ assertEquals("v3/networks/" + NETWORK_ID, route.getValue());
+ verify(client);
+ }
+
+ @Test
+ public void updateCXNetworkStillUsesV2Route() throws Exception {
+ NdexRestClient client = mock(NdexRestClient.class);
+ Capture route = EasyMock.newCapture();
+ expect(client.createReturningConnection(EasyMock.capture(route), anyObject(InputStream.class), eq("PUT"),
+ anyObject())).andReturn(noContentConnection());
+ replay(client);
+
+ new NdexRestClientModelAccessLayer(client).updateCXNetwork(NETWORK_ID, cx2Stream());
+
+ assertEquals("v2/network/" + NETWORK_ID, route.getValue());
+ verify(client);
+ }
+
+ private String captureUpdateRoute(VisibilityType visibility) throws Exception {
+ NdexRestClient client = mock(NdexRestClient.class);
+ Capture route = EasyMock.newCapture();
+ expect(client.createReturningConnection(EasyMock.capture(route), anyObject(InputStream.class), eq("PUT"),
+ anyObject())).andReturn(noContentConnection());
+ replay(client);
+
+ new NdexRestClientModelAccessLayer(client).updateCX2Network(NETWORK_ID, cx2Stream(), visibility);
+
+ verify(client);
+ return route.getValue();
+ }
+
+ // ---------- helpers ----------
+
+ private static InputStream cx2Stream() {
+ return new ByteArrayInputStream("[{\"CXVersion\":\"2.0\"}]".getBytes(StandardCharsets.UTF_8));
+ }
+
+ private static HttpURLConnection createdConnection(UUID newId) throws Exception {
+ HttpURLConnection con = mock(HttpURLConnection.class);
+ expect(con.getResponseCode()).andReturn(HttpURLConnection.HTTP_CREATED).anyTimes();
+ expect(con.getInputStream()).andReturn(new ByteArrayInputStream(
+ ("{\"uuid\":\"" + newId + "\"}").getBytes(StandardCharsets.UTF_8))).anyTimes();
+ replay(con);
+ return con;
+ }
+
+ private static HttpURLConnection noContentConnection() throws Exception {
+ HttpURLConnection con = mock(HttpURLConnection.class);
+ expect(con.getResponseCode()).andReturn(HttpURLConnection.HTTP_NO_CONTENT).anyTimes();
+ replay(con);
+ return con;
+ }
+}