Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
## Version 14.1.6
- Add optional `expireDate` parameter to `doShare` / `doShareAsync`, so an
expiration date can be set when creating a share (issue #76)
- Fix JSON parsing of empty OCS results: the API serializes empty collections
as `[]` instead of `{}`, which caused a `MismatchedInputException` when e.g.
listing users or groups on an empty result (issue #112)
- Testing: integration tests can now auto-provision a throw-away Nextcloud
server via Testcontainers when Docker is available, and are executed in CI
(GitHub Actions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public class JsonAnswerParser<A extends JsonAnswer> implements ConnectorCommon.R
private JsonAnswerParser(Class<A> answerClass) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
// The OCS API serializes empty PHP associative arrays as a JSON array
// "[]" instead of an object "{}". Treat such empty arrays as a null
// object so answers with no data (e.g. an empty user/group list)
// deserialize instead of throwing a MismatchedInputException.
objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);

objectReader = objectMapper.readerFor(answerClass);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.aarboard.nextcloud.api.utils;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.StringReader;
import java.util.List;

import org.aarboard.nextcloud.api.provisioning.GroupListAnswer;
import org.aarboard.nextcloud.api.provisioning.UserDetailsListAnswer;
import org.aarboard.nextcloud.api.provisioning.UserListAnswer;
import org.junit.Test;

/**
* Pure parsing tests (no server required) for the OCS JSON quirk where empty
* collections are serialized as an empty array {@code []} instead of an object.
*
* @author a.schild
*/
public class JsonAnswerParserTest {

/**
* An empty group list comes back with {@code "data":[]} instead of an
* object; it must deserialize to an empty list rather than throwing.
*/
@Test
public void testEmptyGroupListDataAsArray() {
String json = "{\"ocs\":{\"meta\":{\"status\":\"ok\",\"statuscode\":100},\"data\":[]}}";
GroupListAnswer answer = JsonAnswerParser.getInstance(GroupListAnswer.class)
.parseResponse(new StringReader(json));
assertTrue(answer.getAllGroups().isEmpty());
}

/**
* An empty user-details map comes back with {@code "users":[]}; it must
* deserialize to an empty list rather than throwing.
*/
@Test
public void testEmptyUserDetailsUsersAsArray() {
String json = "{\"ocs\":{\"meta\":{\"status\":\"ok\",\"statuscode\":100},\"data\":{\"users\":[]}}}";
UserDetailsListAnswer answer = JsonAnswerParser.getInstance(UserDetailsListAnswer.class)
.parseResponse(new StringReader(json));
assertTrue(answer.getAllUserDetails().isEmpty());
}

/**
* An empty user list comes back with {@code "data":[]}; it must deserialize
* to an empty list rather than throwing.
*/
@Test
public void testEmptyUserListDataAsArray() {
String json = "{\"ocs\":{\"meta\":{\"status\":\"ok\",\"statuscode\":100},\"data\":[]}}";
UserListAnswer answer = JsonAnswerParser.getInstance(UserListAnswer.class)
.parseResponse(new StringReader(json));
assertTrue(answer.getAllUsers().isEmpty());
}

/**
* A populated group list must still deserialize correctly (guards against
* the empty-array handling breaking the normal case).
*/
@Test
public void testPopulatedGroupList() {
String json = "{\"ocs\":{\"meta\":{\"status\":\"ok\",\"statuscode\":100},"
+ "\"data\":{\"groups\":[\"admin\",\"users\"]}}}";
GroupListAnswer answer = JsonAnswerParser.getInstance(GroupListAnswer.class)
.parseResponse(new StringReader(json));
List<String> groups = answer.getAllGroups();
assertEquals(2, groups.size());
assertEquals("admin", groups.get(0));
assertEquals("users", groups.get(1));
}
}
Loading