Commit f3d46e9b authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza

Added v1 api for Networks (Organizations)

replaces blurp with blurb

Some NetworkController improvements

- added @Valid to Organization controller + javax constraints to Organization
- added /details api endpoint
- added some Pre/Post Authorize checks to OrganizationService
- added exception throwing for add/set institutes to Organization
- changed return type of add/set institutes to Organization
- removed @ResponseBody from NetworkController

Added tests, added loadMoreOrganizationInstitutes small bugfixes

fixed preAuth for public apis, OrganizationDetails.institutes now paged

fixed details test
parent 10faa4c0
......@@ -85,7 +85,7 @@ public class OrganizationController extends ApiBaseController {
*/
@RequestMapping(value = "", method = { RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
Object updateOrganization(@RequestBody Organization organizationJson) throws ValidationException {
Organization updateOrganization(@RequestBody Organization organizationJson) throws ValidationException {
final Validator validator = new Validator();
final List<ConstraintViolation> violations = validator.validate(organizationJson);
if (violations.size() > 0) {
......@@ -122,7 +122,7 @@ public class OrganizationController extends ApiBaseController {
public @ResponseBody
String getBlurp(@PathVariable("shortName") String shortName, @PathVariable("language") String language) throws AuthorizationException {
Organization org = organizationService.getOrganization(shortName);
Article article = organizationService.getBlurp(org, new Locale(language));
Article article = organizationService.getBlurb(org, new Locale(language));
return article == null ? null : article.getBody();
}
......@@ -162,7 +162,7 @@ public class OrganizationController extends ApiBaseController {
public @ResponseBody
Set<String> getOrganizationMembers(@PathVariable("shortName") String shortName) throws AuthorizationException {
Set<String> instCodes = new HashSet<String>();
for (FaoInstitute inst : organizationService.getMembers(organizationService.getOrganization(shortName))) {
for (FaoInstitute inst : organizationService.getOrganizationInstitutes(organizationService.getOrganization(shortName))) {
instCodes.add(inst.getCode());
}
return instCodes;
......@@ -177,7 +177,7 @@ public class OrganizationController extends ApiBaseController {
*/
@RequestMapping(value = "/{slug}/add-institutes", method = { RequestMethod.POST, RequestMethod.PUT }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
boolean addOrganizationInstitutes(@PathVariable(value = "slug") String slug, @RequestBody List<String> instituteList) throws JsonProcessingException,
Organization addOrganizationInstitutes(@PathVariable(value = "slug") String slug, @RequestBody List<String> instituteList) throws JsonProcessingException,
IOException {
// TODO Check user's permissions to update this organization.
final Organization organization = organizationService.getOrganization(slug);
......@@ -197,7 +197,7 @@ public class OrganizationController extends ApiBaseController {
*/
@RequestMapping(value = "/{slug}/set-institutes", method = { RequestMethod.POST, RequestMethod.PUT }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
boolean setOrganizationInstitutes(@PathVariable(value = "slug") String slug, @RequestBody List<String> instituteList) throws JsonProcessingException,
Organization setOrganizationInstitutes(@PathVariable(value = "slug") String slug, @RequestBody List<String> instituteList) throws JsonProcessingException,
IOException {
// TODO Check user's permissions to update this organization.
final Organization organization = organizationService.getOrganization(slug);
......
/**
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.genesys2.server.api.v1;
import io.swagger.annotations.Api;
import org.genesys2.server.api.ApiBaseController;
import org.genesys2.server.exception.NotFoundElement;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.model.impl.Organization;
import org.genesys2.server.service.CRMException;
import org.genesys2.server.service.OrganizationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
@RestController("networkApi1")
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = { NetworkController.CONTROLLER_URL, "/json/v1/network" })
@Api(tags = { "network" })
public class NetworkController extends ApiBaseController {
// Rest controller base URL
public static final String CONTROLLER_URL = ApiBaseController.APIv1_BASE + "/network";
@Autowired
private OrganizationService organizationService;
/**
* List organizations
*
* @return
*/
@GetMapping(value = "", produces = { MediaType.APPLICATION_JSON_VALUE })
public List<Organization> listNetworks(@RequestParam(value = "page", required = false, defaultValue = "1") int page) {
return organizationService.list(new PageRequest(page - 1, 50)).getContent();
}
/**
* Create or update organization
*
* @return
*/
@PostMapping(value = "", produces = { MediaType.APPLICATION_JSON_VALUE })
public Organization updateNetwork(@RequestBody Organization organizationJson) {
Organization organization = organizationService.getOrganization(organizationJson.getSlug());
if (organization == null) {
return organizationService.create(organizationJson.getSlug(), organizationJson.getTitle());
}
return organizationService.update(organization.getId(), organization.getSlug(), organizationJson.getTitle());
}
/**
* Get network
*
* @return
*/
@GetMapping(value = "/{shortName}", produces = { MediaType.APPLICATION_JSON_VALUE })
public Organization getNetwork(@PathVariable("shortName") String shortName) {
Organization organization = organizationService.getOrganization(shortName);
if (organization == null)
throw new NotFoundElement("Organization not found");
return organization;
}
/**
* Get network details
*
* @return
*/
@GetMapping(value = "/{shortName}/details", produces = { MediaType.APPLICATION_JSON_VALUE })
public OrganizationDetails getNetworkDetails(@PathVariable("shortName") String shortName, @RequestParam(value = "language", defaultValue = "en") String language) {
Organization organization = organizationService.getOrganization(shortName);
Page<FaoInstitute> members = organizationService.getOrganizationInstitutes(organization, new PageRequest(0, 50));
Article blurb = organizationService.getBlurb(organization, new Locale(language));
return OrganizationDetails.from(organization, blurb, members);
}
/**
* Get organization blurb
*
* @return
*/
@GetMapping(value = "/{shortName}/blurb/{language}", produces = { MediaType.APPLICATION_JSON_VALUE })
public Article getBlurb(@PathVariable("shortName") String shortName, @PathVariable("language") String language) {
Organization org = organizationService.getOrganization(shortName);
return organizationService.getBlurb(org, new Locale(language));
}
/**
* Update blurb
*
*/
@PutMapping(value = "/{shortName}/blurb", produces = { MediaType.APPLICATION_JSON_VALUE })
public Article updateBlurb(@PathVariable("shortName") String shortName, @RequestBody OrganizationBlurbJson blurb) throws CRMException {
Organization org = organizationService.getOrganization(shortName);
return organizationService.updateAbout(org, blurb.blurb, blurb.summary, blurb.getLocale());
}
/**
* Get organization details
*
* @return
*/
@DeleteMapping(value = "/{shortName}", produces = { MediaType.APPLICATION_JSON_VALUE })
public Organization deleteNetwork(@PathVariable("shortName") String shortName) {
return organizationService.deleteOrganization(organizationService.getOrganization(shortName));
}
/**
* Get organization institutes' codes
*
* @return
*/
@GetMapping(value = "/{shortName}/institutes/codes", produces = { MediaType.APPLICATION_JSON_VALUE })
public Set<String> getNetworkInstitutesCodes(@PathVariable("shortName") String shortName) {
Organization org = organizationService.getOrganization(shortName);
return organizationService.getOrganizationInstitutes(org).stream().map(FaoInstitute::getCode).collect(Collectors.toSet());
}
/**
* Get organization details
*
* @return
*/
@GetMapping(value = "/{shortName}/institutes", produces = { MediaType.APPLICATION_JSON_VALUE })
public Page<FaoInstitute> getNetworkInstitutes(@PathVariable("shortName") String shortName, @RequestParam Integer page) {
Organization org = organizationService.getOrganization(shortName);
return organizationService.getOrganizationInstitutes(org, new PageRequest(page, 50));
}
/**
* Add Institutes to Organization
*
* @return
*/
@RequestMapping(value = "/{slug}/add-institutes", method = { RequestMethod.POST, RequestMethod.PUT }, produces = { MediaType.APPLICATION_JSON_VALUE })
public Organization addNetworkInstitutes(@PathVariable(value = "slug") String slug, @RequestBody List<String> instituteList) {
final Organization organization = organizationService.getOrganization(slug);
if (organization == null) {
throw new NotFoundElement("Organization cannot be found");
}
return organizationService.addOrganizationInstitutes(organization, instituteList);
}
/**
* Set Institutes of Organization
*
* @return
*/
@RequestMapping(value = "/{slug}/set-institutes", method = { RequestMethod.POST, RequestMethod.PUT }, produces = { MediaType.APPLICATION_JSON_VALUE })
public Organization setNetworkInstitutes(@PathVariable(value = "slug") String slug, @RequestBody List<String> instituteList) {
final Organization organization = organizationService.getOrganization(slug);
if (organization == null) {
throw new NotFoundElement();
}
return organizationService.setOrganizationInstitutes(organization, instituteList);
}
public static class OrganizationDetails {
public Organization organization;
public Article blurb;
public Page<FaoInstitute> institutes;
public static OrganizationDetails from(Organization organization, Article blurb, Page<FaoInstitute> institutes) {
OrganizationDetails details = new OrganizationDetails();
details.organization = organization;
details.blurb = blurb;
details.institutes = institutes;
return details;
}
}
public static class OrganizationBlurbJson {
public String summary;
public String blurb;
public String locale;
public Locale getLocale() {
return locale == null ? Locale.ENGLISH : new Locale(locale);
}
}
}
......@@ -28,6 +28,8 @@ import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.genesys.blocks.model.AuditedVersionedModel;
import org.hibernate.annotations.Type;
......@@ -40,6 +42,8 @@ public class Organization extends AuditedVersionedModel {
private static final long serialVersionUID = 2710908645431936666L;
@NotNull
@Size(max = 150)
@Column(nullable = false, length = 150, unique = true)
private String slug;
......
......@@ -76,7 +76,7 @@ public class OrganizationController extends BaseController {
if (organization == null) {
throw new NotFoundElement();
}
final List<FaoInstitute> members = organizationService.getMembers(organization);
final List<FaoInstitute> members = organizationService.getOrganizationInstitutes(organization);
LOG.debug("Has: {}", members.size());
......
......@@ -25,34 +25,41 @@ import org.genesys2.server.model.impl.Organization;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import javax.validation.Valid;
public interface OrganizationService {
Page<Organization> list(Pageable pageable);
Organization getOrganization(String slug);
Article updateAbout(Organization organization, String body, String summary, Locale locale) throws CRMException;
Article updateAbout(@Valid Organization organization, String body, String summary, Locale locale) throws CRMException;
Organization update(long id, String newSlug, String title);
Organization update(@Valid Organization organization);
/**
* Create a new organization
*
* @param newSlug
* @param slug
* @param title
* @return
*/
Organization create(String slug, String title);
List<FaoInstitute> getMembers(Organization organization);
Organization create(@Valid Organization organization);
List<FaoInstitute> getOrganizationInstitutes(@Valid Organization organization);
Page<FaoInstitute> getOrganizationInstitutes(@Valid Organization organization, Pageable pageable);
boolean addOrganizationInstitutes(Organization organization, List<String> instituteList);
Organization addOrganizationInstitutes(@Valid Organization organization, List<String> instituteList);
boolean setOrganizationInstitutes(Organization organization, List<String> instituteList);
Organization setOrganizationInstitutes(@Valid Organization organization, List<String> instituteList);
Organization deleteOrganization(Organization organization);
Organization deleteOrganization(@Valid Organization organization);
Article getBlurp(Organization organization, Locale locale);
Article getBlurb(Organization organization, Locale locale);
List<Organization> getOrganizations(FaoInstitute institute);
}
......@@ -743,7 +743,7 @@ public class GenesysServiceImpl implements GenesysService, DatasetService {
@Override
public Page<Accession> listAccessionsByOrganization(Organization organization, Pageable pageable) {
final List<FaoInstitute> members = organizationService.getMembers(organization);
final List<FaoInstitute> members = organizationService.getOrganizationInstitutes(organization);
if (members == null || members.size() == 0) {
return new PageImpl<Accession>(Collections.emptyList());
}
......@@ -1617,7 +1617,7 @@ public class GenesysServiceImpl implements GenesysService, DatasetService {
public PDCIStatistics statisticsPDCI(Organization organization) {
PDCIStatistics stats = new PDCIStatistics();
for (FaoInstitute faoInstitute : organizationService.getMembers(organization)) {
for (FaoInstitute faoInstitute : organizationService.getOrganizationInstitutes(organization)) {
stats.merge(faoInstitute.getStatisticsPDCI());
}
return stats;
......
......@@ -22,9 +22,12 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.genesys2.server.exception.InvalidApiUsageException;
import org.genesys2.server.exception.NotFoundElement;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.model.impl.Organization;
import org.genesys2.server.model.impl.QFaoInstitute;
import org.genesys2.server.persistence.FaoInstituteRepository;
import org.genesys2.server.persistence.OrganizationRepository;
import org.genesys2.server.service.CRMException;
......@@ -37,12 +40,17 @@ import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
@Service
@Transactional(readOnly = true)
@Validated
public class OrganizationServiceImpl implements OrganizationService {
private static final String CACHE_ORGANIZATION_INSTITUTEORGS = "hibernate.org.genesys2.server.model.impl.Organization.instituteOrganizations";
......@@ -59,14 +67,14 @@ public class OrganizationServiceImpl implements OrganizationService {
@Override
public Organization getOrganization(String slug) {
return organizationRepository.findBySlug(slug);
return lazyLoad(organizationRepository.findBySlug(slug));
}
@PreAuthorize("hasRole('ADMINISTRATOR')")
@Override
@Transactional
@CacheEvict(value = CACHE_ORGANIZATION_INSTITUTEORGS, allEntries = true)
public Organization deleteOrganization(Organization organization) {
public Organization deleteOrganization(@Valid Organization organization) {
organization = organizationRepository.findOne(organization.getId());
organizationRepository.delete(organization);
organization.setId(null);
......@@ -79,29 +87,44 @@ public class OrganizationServiceImpl implements OrganizationService {
}
@Override
public Article getBlurp(Organization organization, Locale locale) {
return contentService.getArticle(organization, "blurp", locale);
public Article getBlurb(Organization organization, Locale locale) {
return contentService.getArticle(organization, "blurb", locale);
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PreAuthorize("hasRole('ADMINISTRATOR') || hasPermission(organization, 'write')")
@Transactional(readOnly = false)
public Article updateAbout(Organization organization, String body, String summary, Locale locale) throws CRMException {
return contentService.updateArticle(organization, "blurp", null, body, summary, locale);
public Article updateAbout(@Valid Organization organization, String body, String summary, Locale locale) throws CRMException {
return contentService.updateArticle(organization, "blurb", null, body, summary, locale);
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
@Transactional(readOnly = false)
public Organization update(long id, String newSlug, String title) {
final Organization organization = organizationRepository.findOne(id);
final Organization organization = new Organization();
organization.setId(id);
organization.setSlug(newSlug);
organization.setTitle(title);
organizationRepository.save(organization);
return organization;
return update(organization);
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') || hasPermission(organization, 'write')")
@Transactional(readOnly = false)
public Organization update(@Valid Organization organization) {
final Organization toUpdate = organizationRepository.findBySlug(organization.getSlug());
if (toUpdate == null)
throw new NotFoundElement("Organization not found");
toUpdate.setSlug(organization.getSlug());
toUpdate.setTitle(organization.getTitle());
return lazyLoad(organizationRepository.save(toUpdate));
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
@Transactional(readOnly = false)
......@@ -110,24 +133,38 @@ public class OrganizationServiceImpl implements OrganizationService {
organization.setSlug(slug);
organization.setTitle(title);
organizationRepository.save(organization);
return create(organization);
}
return organization;
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
@Transactional(readOnly = false)
public Organization create(@Valid Organization organization) {
final Organization toCreate = new Organization();
toCreate.setSlug(organization.getSlug());
toCreate.setTitle(organization.getTitle());
return organizationRepository.save(toCreate);
}
@Override
public List<FaoInstitute> getMembers(Organization organization) {
public List<FaoInstitute> getOrganizationInstitutes(@Valid Organization organization) {
if (organization == null) {
throw new NullPointerException("organization");
throw new InvalidApiUsageException("Organization cannot be null");
}
return organizationRepository.findInstitutesByOrganization(organization);
}
@Override
public Page<FaoInstitute> getOrganizationInstitutes(Organization organization, Pageable pageable) {
return instituteRepository.findAll(QFaoInstitute.faoInstitute.in(organization.getMembers()), pageable);
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PreAuthorize("hasRole('ADMINISTRATOR') || hasPermission(org, 'write')")
@CacheEvict(value = CACHE_ORGANIZATION_INSTITUTEORGS, allEntries = true)
public boolean setOrganizationInstitutes(Organization org, List<String> instituteList) {
public Organization setOrganizationInstitutes(@Valid Organization org, List<String> instituteList) {
LOG.info("Setting institutes for organization {}", org);
final Organization organization = organizationRepository.findOne(org.getId());
......@@ -167,19 +204,19 @@ public class OrganizationServiceImpl implements OrganizationService {
}
}
if (updated) {
LOG.info("Saving {}", organization);
organizationRepository.save(organization);
if (!updated) {
throw new InvalidApiUsageException("Organization cannot be updated");
}
return updated;
LOG.info("Saving {}", organization);
return lazyLoad(organizationRepository.save(organization));
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PreAuthorize("hasRole('ADMINISTRATOR') || hasPermission(organization, 'write')")
@CacheEvict(value = CACHE_ORGANIZATION_INSTITUTEORGS, allEntries=true)
public boolean addOrganizationInstitutes(final Organization organization, final List<String> instituteList) {
public Organization addOrganizationInstitutes(@Valid final Organization organization, final List<String> instituteList) {
LOG.info("Adding institutes to organization {}", organization);
// Make set of INSTCODEs
......@@ -202,11 +239,11 @@ public class OrganizationServiceImpl implements OrganizationService {
}
}
if (updated) {
organizationRepository.save(organization);
if (!updated) {
throw new InvalidApiUsageException("Organization cannot be updated");
}
return updated;
return lazyLoad(organizationRepository.save(organization));
}
@Override
......@@ -217,4 +254,14 @@ public class OrganizationServiceImpl implements OrganizationService {
}
return organizationRepository.getOrganizations(faoInstitute);
}
private Organization lazyLoad(Organization input) {
if (input == null)
return null;
if (input.getMembers() != null)
input.getMembers().size();
return input;
}
}
package org.genesys.test.server.services;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import com.google.common.collect.Lists;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.model.impl.Organization;
import org.genesys2.server.persistence.ArticleRepository;
import org.genesys2.server.persistence.FaoInstituteRepository;
import org.genesys2.server.persistence.OrganizationRepository;
import org.genesys2.server.service.CRMException;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.OrganizationService;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Locale;
public class OrganizationServiceTest extends AbstractServicesTest {
protected static final String SLUG_1 = "SLUG_1";
protected static final String TITLE_1 = "TITLE_1";
protected static final String SLUG_2 = "SLUG_2";
protected static final String TITLE_2 = "TITLE_2";
protected static final String INST_CODE_1 = "CODE001";
protected static final String INST_CODE_2 = "CODE002";
protected static final String BLURB_TITLE_1 = "BLURB_TITLE_1";
protected static final String BLURB_BODY_1 = "BLURB_BODY_1";
protected static final String BLURB_SUMMARY_1 = "BLURB_SUMMARY_1";
@Autowired
private ArticleRepository articleRepository;
@Autowired
private ContentService contentService;
@Autowired
private FaoInstituteRepository instituteRepository;
@Autowired
private OrganizationService organizationService;
@Autowired
private OrganizationRepository organizationRepository;
@Transactional
@Override
@After
public void cleanup() {
articleRepository.deleteAll();
organizationRepository.deleteAll();
instituteRepository.deleteAll();
}
@Test
public void testUpdate() {
Organization toUpdate = setupOrganization(SLUG_1, TITLE_1);
final String newTitle = "NEW_TITLE";
organizationService.update(toUpdate.getId(), SLUG_1, newTitle);
Organization updated = organizationService.getOrganization(SLUG_1);
assertEquals(updated.getId(), toUpdate.getId());
assertEquals(updated.getTitle(), newTitle);
}
@Test(expected = javax.validation.ConstraintViolationException.class)
public void testUpdateWithNullShouldFail() {
Organization toUpdate = setupOrganization(SLUG_1, TITLE_1);
final String newSlug = null;
toUpdate.setSlug(newSlug);
organizationService.update(toUpdate);
fail();
}
@Test(expected = javax.validation.ConstraintViolationException.class)
public void testCreateWithNullShouldFail() {
final String newSlug = null;
final String newTitle = "doesn't matter";
organizationService.create(newSlug, newTitle);
fail();
}
@Test
public void testGetInstitutes() {
Organization organization = setupOrganization(SLUG_1, TITLE_1);
FaoInstitute faoInstitute1 = setupInstitute(INST_CODE_1);
FaoInstitute faoInstitute2 = setupInstitute(INST_CODE_2);
organizationService.setOrganizationInstitutes(organization, Lists.newArrayList(faoInstitute1.getCode(), faoInstitute2.getCode()));
List<FaoInstitute> institutes = organizationService.getOrganizationInstitutes(organization);
assertEquals(2, institutes.size());
assertThat(institutes, containsInAnyOrder(faoInstitute1, faoInstitute2));