diff --git a/Changelog.md b/Changelog.md index bbfd144..25d9bd9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,12 @@ # Changelog for nextcloud api +## Version 14.2.0 +- Add support for the Group Folders app: create, rename, delete and list group + folders, grant/revoke group access, set group permissions and set the folder + quota via the new `GroupFolders` connector and `NextcloudConnector` methods + (issue #109). Thanks to Denis Verkhovsky for the original implementation the + port is based on. + ## Version 14.1.6 - 2026-07-24 - Add optional `expireDate` parameter to `doShare` / `doShareAsync`, so an diff --git a/src/main/java/org/aarboard/nextcloud/api/NextcloudConnector.java b/src/main/java/org/aarboard/nextcloud/api/NextcloudConnector.java index 488311a..1b2bb14 100644 --- a/src/main/java/org/aarboard/nextcloud/api/NextcloudConnector.java +++ b/src/main/java/org/aarboard/nextcloud/api/NextcloudConnector.java @@ -31,6 +31,8 @@ import org.aarboard.nextcloud.api.filesharing.ShareType; import org.aarboard.nextcloud.api.filesharing.SharesXMLAnswer; import org.aarboard.nextcloud.api.filesharing.SingleShareXMLAnswer; +import org.aarboard.nextcloud.api.groupfolders.GroupFolderInfo; +import org.aarboard.nextcloud.api.groupfolders.GroupFolders; import org.aarboard.nextcloud.api.provisioning.*; import org.aarboard.nextcloud.api.utils.*; import org.aarboard.nextcloud.api.webdav.Files; @@ -48,6 +50,7 @@ public class NextcloudConnector implements AutoCloseable { private final ConfigConnector cc; private final Folders fd; private final Files fl; + private final GroupFolders gf; /** * @@ -110,6 +113,7 @@ public NextcloudConnector(String originalServiceUrl, AuthenticationConfig authen cc = new ConfigConnector(this.serverConfig); fd = new Folders(this.serverConfig); fl = new Files(this.serverConfig); + gf = new GroupFolders(this.serverConfig); } catch (MalformedURLException e) { throw new IllegalArgumentException(e); @@ -131,6 +135,7 @@ public NextcloudConnector(String serverName, boolean useHTTPS, int port, cc = new ConfigConnector(this.serverConfig); fd = new Folders(this.serverConfig); fl = new Files(this.serverConfig); + gf = new GroupFolders(this.serverConfig); } /** @@ -189,6 +194,84 @@ public void shutdown() throws IOException { ConnectorCommon.shutdown(); } + /** + * Creates a new group folder (Group Folders app must be installed). + * + * @param mountPoint name/mount point of the group folder + * @return the id of the newly created group folder + */ + public int createGroupFolder(String mountPoint) { + return gf.createGroupFolder(mountPoint); + } + + /** + * Renames (changes the mount point of) a group folder. + * + * @param groupFolderId id of the group folder + * @param newMountPoint new name/mount point + */ + public void renameGroupFolder(int groupFolderId, String newMountPoint) { + gf.renameGroupFolder(groupFolderId, newMountPoint); + } + + /** + * Deletes a group folder. + * + * @param groupFolderId id of the group folder + */ + public void deleteGroupFolder(int groupFolderId) { + gf.deleteGroupFolder(groupFolderId); + } + + /** + * @return all group folders visible to the current user + */ + public Collection getGroupFolders() { + return gf.getGroupFolders(); + } + + /** + * Grants a group access to a group folder. + * + * @param groupFolderId id of the group folder + * @param group group id to grant access to + */ + public void grantAccessToGroupFolder(int groupFolderId, String group) { + gf.grantAccess(groupFolderId, group); + } + + /** + * Revokes a group's access to a group folder. + * + * @param groupFolderId id of the group folder + * @param group group id to revoke access from + */ + public void revokeAccessToGroupFolder(int groupFolderId, String group) { + gf.revokeAccess(groupFolderId, group); + } + + /** + * Sets the permissions a group has on a group folder. + * + * @param groupFolderId id of the group folder + * @param group group id + * @param permissions permission bit mask (1 = read, 2 = update, 4 = + * create, 8 = delete, 16 = share, 31 = all) + */ + public void setGroupFolderPermissions(int groupFolderId, String group, int permissions) { + gf.setGroupPermissions(groupFolderId, group, permissions); + } + + /** + * Sets the quota of a group folder. + * + * @param groupFolderId id of the group folder + * @param quota quota in bytes, or {@code -3} for an unlimited quota + */ + public void setGroupFolderQuota(int groupFolderId, long quota) { + gf.setQuota(groupFolderId, quota); + } + /** * Close the HTTP client. Perform this to cleanly shut down this * application. diff --git a/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFolderAnswer.java b/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFolderAnswer.java new file mode 100644 index 0000000..53dbd11 --- /dev/null +++ b/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFolderAnswer.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2026 a.schild + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.aarboard.nextcloud.api.groupfolders; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.aarboard.nextcloud.api.utils.JsonAnswer; + +/** + * Answer of the "create group folder" call. The server returns the full + * created folder, of which we expose the id. + */ +public class GroupFolderAnswer extends JsonAnswer { + + @JsonProperty + private GroupFolderInfo data; + + @JsonIgnore + public Integer getId() { + if (data != null) { + return data.getId(); + } + return null; + } +} diff --git a/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFolderInfo.java b/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFolderInfo.java new file mode 100644 index 0000000..ceca4b4 --- /dev/null +++ b/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFolderInfo.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2026 a.schild + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.aarboard.nextcloud.api.groupfolders; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; + +/** + * Information about a single group folder as returned by the + * Group Folders app. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class GroupFolderInfo { + + @JsonProperty + private Integer id; + @JsonProperty + private String mount_point; + @JsonProperty + private Map groups; + @JsonProperty + private Long quota; + + /** + * @return the id of the group folder + */ + @JsonIgnore + public Integer getId() { + return id; + } + + /** + * @return the mount point (name) of the group folder + */ + @JsonIgnore + public String getMountPoint() { + return mount_point; + } + + /** + * @return the assigned groups mapped to their permission bit mask + */ + @JsonIgnore + public Map getAssignedGroups() { + return groups; + } + + /** + * @return the quota of the group folder in bytes, or {@code -3} for an + * unlimited quota (see the Group Folders app documentation) + */ + @JsonIgnore + public Long getQuota() { + return quota; + } +} diff --git a/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFolders.java b/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFolders.java new file mode 100644 index 0000000..9d5b9fc --- /dev/null +++ b/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFolders.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2026 a.schild + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.aarboard.nextcloud.api.groupfolders; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.aarboard.nextcloud.api.ServerConfig; +import org.aarboard.nextcloud.api.utils.ConnectorCommon; +import org.aarboard.nextcloud.api.utils.EmptyAnswerParser; +import org.aarboard.nextcloud.api.utils.JsonAnswerParser; +import org.aarboard.nextcloud.api.utils.NextcloudResponseHelper; +import org.aarboard.nextcloud.api.utils.XMLAnswer; +import org.aarboard.nextcloud.api.utils.XMLAnswerParser; +import org.apache.http.NameValuePair; +import org.apache.http.message.BasicNameValuePair; + +/** + * Access to the Group + * Folders app API. The app must be installed and enabled on the server. + * + * @author a.schild + */ +public class GroupFolders { + + private static final String GROUP_FOLDERS_ROOT = "index.php/apps/groupfolders/folders"; + + private final ConnectorCommon connectorCommon; + + public GroupFolders(ServerConfig serverConfig) { + this.connectorCommon = new ConnectorCommon(serverConfig); + } + + /** + * Creates a new group folder with the given mount point (name). + * + * @param mountPoint name/mount point of the group folder + * @return the id of the newly created group folder + */ + public int createGroupFolder(String mountPoint) { + return NextcloudResponseHelper.getAndCheckStatus(createGroupFolderAsync(mountPoint)).getId(); + } + + public CompletableFuture createGroupFolderAsync(String mountPoint) { + List postParams = new ArrayList<>(); + postParams.add(new BasicNameValuePair("mountpoint", mountPoint)); + return connectorCommon.executePost(GROUP_FOLDERS_ROOT, postParams, + JsonAnswerParser.getInstance(GroupFolderAnswer.class)); + } + + /** + * Renames (changes the mount point of) a group folder. + * + * @param groupFolderId id of the group folder + * @param newMountPoint new name/mount point + */ + public void renameGroupFolder(int groupFolderId, String newMountPoint) { + NextcloudResponseHelper.getAndCheckStatus(renameGroupFolderAsync(groupFolderId, newMountPoint)); + } + + public CompletableFuture renameGroupFolderAsync(int groupFolderId, String newMountPoint) { + List postParams = new ArrayList<>(); + postParams.add(new BasicNameValuePair("mountpoint", newMountPoint)); + String url = String.format("%s/%d/mountpoint", GROUP_FOLDERS_ROOT, groupFolderId); + return connectorCommon.executePost(url, postParams, XMLAnswerParser.getInstance(XMLAnswer.class)); + } + + /** + * Deletes a group folder. + * + * @param groupFolderId id of the group folder + */ + public void deleteGroupFolder(int groupFolderId) { + NextcloudResponseHelper.getAndCheckStatus(deleteGroupFolderAsync(groupFolderId)); + } + + public CompletableFuture deleteGroupFolderAsync(int groupFolderId) { + return connectorCommon.executeDelete(GROUP_FOLDERS_ROOT, String.valueOf(groupFolderId), + XMLAnswerParser.getInstance(XMLAnswer.class)); + } + + /** + * @return all group folders visible to the current user + */ + public Collection getGroupFolders() { + return NextcloudResponseHelper.getAndCheckStatus(getGroupFoldersAsync()).getAllGroupFolders(); + } + + public CompletableFuture getGroupFoldersAsync() { + return connectorCommon.executeGet(GROUP_FOLDERS_ROOT, + JsonAnswerParser.getInstance(GroupFoldersListAnswer.class)); + } + + /** + * Grants a group access to a group folder. + * + * @param groupFolderId id of the group folder + * @param group group id to grant access to + */ + public void grantAccess(int groupFolderId, String group) { + NextcloudResponseHelper.getAndCheckStatus(grantAccessAsync(groupFolderId, group)); + } + + public CompletableFuture grantAccessAsync(int groupFolderId, String group) { + List postParams = new ArrayList<>(); + postParams.add(new BasicNameValuePair("group", group)); + String url = String.format("%s/%d/groups", GROUP_FOLDERS_ROOT, groupFolderId); + return connectorCommon.executePost(url, postParams, XMLAnswerParser.getInstance(XMLAnswer.class)); + } + + /** + * Revokes a group's access to a group folder. + * + * @param groupFolderId id of the group folder + * @param group group id to revoke access from + */ + public void revokeAccess(int groupFolderId, String group) { + NextcloudResponseHelper.getAndCheckStatus(revokeAccessAsync(groupFolderId, group)); + } + + public CompletableFuture revokeAccessAsync(int groupFolderId, String group) { + return connectorCommon.executeDelete(GROUP_FOLDERS_ROOT, + String.format("%d/groups/%s", groupFolderId, group), + XMLAnswerParser.getInstance(XMLAnswer.class)); + } + + /** + * Sets the permissions a group has on a group folder. + * + * @param groupFolderId id of the group folder + * @param group group id + * @param permissions permission bit mask (1 = read, 2 = update, 4 = + * create, 8 = delete, 16 = share, 31 = all) + */ + public void setGroupPermissions(int groupFolderId, String group, int permissions) { + NextcloudResponseHelper.getAndCheckStatus(setGroupPermissionsAsync(groupFolderId, group, permissions)); + } + + public CompletableFuture setGroupPermissionsAsync(int groupFolderId, String group, int permissions) { + List postParams = new ArrayList<>(); + postParams.add(new BasicNameValuePair("permissions", String.valueOf(permissions))); + String url = String.format("%s/%d/groups/%s", GROUP_FOLDERS_ROOT, groupFolderId, group); + return connectorCommon.executePost(url, postParams, XMLAnswerParser.getInstance(XMLAnswer.class)); + } + + /** + * Sets the quota of a group folder. + * + * @param groupFolderId id of the group folder + * @param quota quota in bytes, or {@code -3} for an unlimited quota + */ + public void setQuota(int groupFolderId, long quota) { + NextcloudResponseHelper.getAndCheckStatus(setQuotaAsync(groupFolderId, quota)); + } + + public CompletableFuture setQuotaAsync(int groupFolderId, long quota) { + List postParams = new ArrayList<>(); + postParams.add(new BasicNameValuePair("quota", String.valueOf(quota))); + String url = String.format("%s/%d/quota", GROUP_FOLDERS_ROOT, groupFolderId); + return connectorCommon.executePost(url, postParams, XMLAnswerParser.getInstance(XMLAnswer.class)); + } +} diff --git a/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFoldersListAnswer.java b/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFoldersListAnswer.java new file mode 100644 index 0000000..c76f004 --- /dev/null +++ b/src/main/java/org/aarboard/nextcloud/api/groupfolders/GroupFoldersListAnswer.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2026 a.schild + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.aarboard.nextcloud.api.groupfolders; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import org.aarboard.nextcloud.api.utils.JsonAnswer; + +/** + * Answer of the "list group folders" call. The server returns {@code data} as + * an object keyed by the folder id, which Jackson maps directly into a map (an + * empty result comes back as {@code []} and is handled as a null map by the + * shared object mapper). + */ +public class GroupFoldersListAnswer extends JsonAnswer { + + @JsonProperty + private Map data; + + @JsonIgnore + public Collection getAllGroupFolders() { + if (data != null) { + return data.values(); + } + return Collections.emptyList(); + } +} diff --git a/src/main/java/org/aarboard/nextcloud/api/utils/EmptyAnswerParser.java b/src/main/java/org/aarboard/nextcloud/api/utils/EmptyAnswerParser.java new file mode 100644 index 0000000..35fdb85 --- /dev/null +++ b/src/main/java/org/aarboard/nextcloud/api/utils/EmptyAnswerParser.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2026 a.schild + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.aarboard.nextcloud.api.utils; + +import java.io.Reader; + +/** + * Response parser for endpoints that return no meaningful body (e.g. some of + * the group folders endpoints). The response is ignored and {@code null} is + * returned. + */ +public class EmptyAnswerParser implements ConnectorCommon.ResponseParser { + + private static volatile EmptyAnswerParser instance; + + public static EmptyAnswerParser getInstance() { + if (instance == null) { + synchronized (EmptyAnswerParser.class) { + if (instance == null) { + instance = new EmptyAnswerParser(); + } + } + } + return instance; + } + + @Override + public Void parseResponse(Reader reader) { + return null; + } +} diff --git a/src/test/java/org/aarboard/nextcloud/api/NextcloudTestContainer.java b/src/test/java/org/aarboard/nextcloud/api/NextcloudTestContainer.java index d0b8816..05590e2 100644 --- a/src/test/java/org/aarboard/nextcloud/api/NextcloudTestContainer.java +++ b/src/test/java/org/aarboard/nextcloud/api/NextcloudTestContainer.java @@ -110,7 +110,12 @@ public static synchronized void ensureStarted() { .forResponsePredicate(body -> body.contains("\"installed\":true")) .withStartupTimeout(Duration.ofMinutes(5))); container.start(); - disablePasswordPolicy(); + // Create test users with fixed simple passwords without the default + // password policy rejecting them. + occ("app:disable", "password_policy"); + // The Group Folders app is not bundled; install it so the group folder + // tests have something to talk to. + occ("app:install", "groupfolders"); System.setProperty("nextcloud.api.test.servername", container.getHost()); System.setProperty("nextcloud.api.test.serverport", String.valueOf(container.getMappedPort(80))); @@ -119,24 +124,29 @@ public static synchronized void ensureStarted() { } /** - * Disables the {@code password_policy} app so the tests, which create users - * with fixed simple passwords, are not rejected by Nextcloud's default - * password/complexity/breach checks. Best-effort: a failure here is logged - * but does not abort the suite. occ must run as the {@code www-data} user - * (it refuses to run as root). + * Runs an {@code occ} command inside the container to configure the server + * for the tests. Best-effort: a failure is logged but does not abort the + * suite. occ must run as the {@code www-data} user (it refuses to run as + * root). + * + * @param args the occ command and its arguments */ - private static void disablePasswordPolicy() { + private static void occ(String... args) { + String[] command = new String[args.length + 2]; + command[0] = "php"; + command[1] = "occ"; + System.arraycopy(args, 0, command, 2, args.length); try { ExecResult result = container.execInContainer(ExecConfig.builder() .user("www-data") - .command(new String[] {"php", "occ", "app:disable", "password_policy"}) + .command(command) .build()); if (result.getExitCode() != 0) { - System.err.println("Could not disable password_policy app: " + System.err.println("occ " + String.join(" ", args) + " failed: " + result.getStdout() + result.getStderr()); } } catch (IOException | InterruptedException e) { - System.err.println("Could not disable password_policy app: " + e.getMessage()); + System.err.println("occ " + String.join(" ", args) + " failed: " + e.getMessage()); if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } diff --git a/src/test/java/org/aarboard/nextcloud/api/TestGroupFolders.java b/src/test/java/org/aarboard/nextcloud/api/TestGroupFolders.java new file mode 100644 index 0000000..5b790b9 --- /dev/null +++ b/src/test/java/org/aarboard/nextcloud/api/TestGroupFolders.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2026 a.schild + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.aarboard.nextcloud.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Optional; + +import org.aarboard.nextcloud.api.groupfolders.GroupFolderInfo; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +/** + * Integration tests for the Group Folders app support. Requires the + * {@code groupfolders} app to be installed on the test server (the test + * container installs it automatically). + * + * @author a.schild + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestGroupFolders { + + private static final String TEST_GROUP_FOLDER = "api-test-group-folder"; + private static final String TEST_GROUP_FOLDER_RENAMED = "api-test-group-folder-renamed"; + private static final String ADMIN_GROUP = "admin"; + + private static String serverName = null; + private static NextcloudConnector _nc = null; + + private static int groupFolderId = -1; + + @BeforeClass + public static void setUp() { + TestHelper th = new TestHelper(); + serverName = th.getServerName(); + if (serverName != null) { + _nc = new NextcloudConnector(serverName, th.getServerPort() == 443, th.getServerPort(), + th.getUserName(), th.getPassword()); + } + } + + @AfterClass + public static void tearDown() { + if (_nc != null && groupFolderId != -1) { + try { + _nc.deleteGroupFolder(groupFolderId); + } catch (Exception ex) { + // best effort cleanup + } + } + } + + @Test + public void t01_testCreateGroupFolder() { + if (_nc != null) { + groupFolderId = _nc.createGroupFolder(TEST_GROUP_FOLDER); + assertTrue(groupFolderId > 0); + } + } + + @Test + public void t02_testGetGroupFolders() { + if (_nc != null) { + Optional folder = findTestFolder(); + assertTrue(folder.isPresent()); + assertEquals(TEST_GROUP_FOLDER, folder.get().getMountPoint()); + } + } + + @Test + public void t03_testRenameGroupFolder() { + if (_nc != null) { + _nc.renameGroupFolder(groupFolderId, TEST_GROUP_FOLDER_RENAMED); + GroupFolderInfo folder = getTestFolderById(); + assertNotNull(folder); + assertEquals(TEST_GROUP_FOLDER_RENAMED, folder.getMountPoint()); + } + } + + @Test + public void t04_testGrantAccess() { + if (_nc != null) { + _nc.grantAccessToGroupFolder(groupFolderId, ADMIN_GROUP); + GroupFolderInfo folder = getTestFolderById(); + assertNotNull(folder); + assertTrue(folder.getAssignedGroups().containsKey(ADMIN_GROUP)); + } + } + + @Test + public void t05_testSetPermissions() { + if (_nc != null) { + // read + update + _nc.setGroupFolderPermissions(groupFolderId, ADMIN_GROUP, 3); + GroupFolderInfo folder = getTestFolderById(); + assertNotNull(folder); + assertEquals(Integer.valueOf(3), folder.getAssignedGroups().get(ADMIN_GROUP)); + } + } + + @Test + public void t06_testSetQuota() { + if (_nc != null) { + long quota = 5L * 1024 * 1024 * 1024; // 5 GB + _nc.setGroupFolderQuota(groupFolderId, quota); + GroupFolderInfo folder = getTestFolderById(); + assertNotNull(folder); + assertEquals(Long.valueOf(quota), folder.getQuota()); + } + } + + @Test + public void t07_testRevokeAccess() { + if (_nc != null) { + _nc.revokeAccessToGroupFolder(groupFolderId, ADMIN_GROUP); + GroupFolderInfo folder = getTestFolderById(); + assertNotNull(folder); + assertTrue(folder.getAssignedGroups() == null + || !folder.getAssignedGroups().containsKey(ADMIN_GROUP)); + } + } + + @Test + public void t08_testDeleteGroupFolder() { + if (_nc != null) { + _nc.deleteGroupFolder(groupFolderId); + int deletedId = groupFolderId; + groupFolderId = -1; + assertTrue(_nc.getGroupFolders().stream().noneMatch(f -> f.getId() == deletedId)); + } + } + + private Optional findTestFolder() { + return _nc.getGroupFolders().stream() + .filter(f -> f.getId() != null && f.getId() == groupFolderId) + .findFirst(); + } + + private GroupFolderInfo getTestFolderById() { + return findTestFolder().orElse(null); + } +}