Move the content of the full folder to the root folder (close #644).
Change-Id: Id9243839b6288fa4cd9ea250ad9f659a0ece613c
diff --git a/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java b/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java
new file mode 100644
index 0000000..eeb3ea6
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java
@@ -0,0 +1,69 @@
+package de.ids_mannheim.korap.authentication;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import com.nimbusds.jose.JOSEException;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.constant.TokenType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.security.context.TokenContext;
+import de.ids_mannheim.korap.user.KorAPUser;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import de.ids_mannheim.korap.web.controller.OAuth2TestBase;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+public class APIAuthenticationTest extends OAuth2TestBase {
+
+ @Autowired
+ private FullConfiguration config;
+
+ @Test
+ public void testDeprecatedService () throws KustvaktException {
+ String userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ Response response = target().path(API_VERSION).path("auth")
+ .path("apiToken").request()
+ .header(Attributes.AUTHORIZATION, userAuthHeader)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.DEPRECATED, node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testCreateGetTokenContext () throws KustvaktException,
+ IOException, InterruptedException, JOSEException {
+ User user = new KorAPUser();
+ user.setUsername("testUser");
+ Map<String, Object> attr = new HashMap<>();
+ attr.put(Attributes.HOST, "localhost");
+ attr.put(Attributes.USER_AGENT, "java");
+ attr.put(Attributes.AUTHENTICATION_TIME, TimeUtils.getNow().toDate());
+ APIAuthentication auth = new APIAuthentication(config);
+ TokenContext context = auth.createTokenContext(user, attr);
+ // get token context
+ String authToken = context.getToken();
+ // System.out.println(authToken);
+ context = auth.getTokenContext(authToken);
+ TokenType tokenType = context.getTokenType();
+ assertEquals(TokenType.API, tokenType);
+ assertEquals(context.getUsername(), "testUser");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/authentication/AuthenticationFilterTest.java b/src/test/java/de/ids_mannheim/korap/authentication/AuthenticationFilterTest.java
new file mode 100644
index 0000000..9e6f120
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/authentication/AuthenticationFilterTest.java
@@ -0,0 +1,29 @@
+package de.ids_mannheim.korap.authentication;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.core.Response;
+
+public class AuthenticationFilterTest extends SpringJerseyTest {
+
+ @Test
+ public void testAuthenticationWithUnknownScheme ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .request().header(Attributes.AUTHORIZATION, "Blah blah").get();
+ String entity = response.readEntity(String.class);
+ JsonNode n = JsonUtils.readTree(entity);
+ assertEquals(n.at("/errors/0/0").asText(), "2001");
+ assertEquals(n.at("/errors/0/1").asText(),
+ "Authentication scheme is not supported.");
+ assertEquals(n.at("/errors/0/2").asText(), "Blah");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/authentication/LdapAuth3Test.java b/src/test/java/de/ids_mannheim/korap/authentication/LdapAuth3Test.java
new file mode 100644
index 0000000..0580bba
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/authentication/LdapAuth3Test.java
@@ -0,0 +1,232 @@
+package de.ids_mannheim.korap.authentication;
+
+import com.unboundid.ldap.listener.InMemoryDirectoryServer;
+import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
+import com.unboundid.ldap.listener.InMemoryListenerConfig;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.util.Base64;
+import com.unboundid.util.StaticUtils;
+import com.unboundid.util.ssl.KeyStoreKeyManager;
+import com.unboundid.util.ssl.SSLUtil;
+import com.unboundid.util.ssl.TrustAllTrustManager;
+import com.unboundid.util.ssl.TrustStoreTrustManager;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.security.GeneralSecurityException;
+
+import static de.ids_mannheim.korap.authentication.LdapAuth3.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class LdapAuth3Test {
+
+ public static final String TEST_LDAP_CONF = "src/test/resources/test-ldap.conf";
+
+ public static final String TEST_LDAPS_CONF = "src/test/resources/test-ldaps.conf";
+
+ public static final String TEST_LDAPS_TS_CONF = "src/test/resources/test-ldaps-with-truststore.conf";
+
+ public static final String TEST_LDAP_USERS_LDIF = "src/test/resources/test-ldap-users.ldif";
+
+ private static final String keyStorePath = "src/test/resources/keystore.p12";
+
+ static InMemoryDirectoryServer server;
+
+ @BeforeAll
+ static void startDirectoryServer ()
+ throws LDAPException, GeneralSecurityException {
+ InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(
+ "dc=example,dc=com");
+ config.addAdditionalBindCredentials("cn=admin,dc=example,dc=com",
+ "adminpassword");
+ config.setSchema(null);
+ final SSLUtil serverSSLUtil = new SSLUtil(
+ new KeyStoreKeyManager(keyStorePath, "password".toCharArray(),
+ "PKCS12", "server-cert"),
+ new TrustStoreTrustManager(keyStorePath));
+ final SSLUtil clientSslUtil = new SSLUtil(new TrustAllTrustManager());
+ config.setListenerConfigs(// Listener name
+ InMemoryListenerConfig.createLDAPConfig(// Listener name
+ "LDAP", // Listen address. (null = listen on all interfaces)
+ null, // Listen port (0 = automatically choose an available port)
+ 3268, // StartTLS factory
+ clientSslUtil.createSSLSocketFactory()), // Listener name
+ InMemoryListenerConfig.createLDAPSConfig(// Listener name
+ "LDAPS", // Listen address. (null = listen on all interfaces)
+ null, // Listen port (0 = automatically choose an available port)
+ 3269, serverSSLUtil.createSSLServerSocketFactory(),
+ clientSslUtil.createSSLSocketFactory()));
+ server = new InMemoryDirectoryServer(config);
+ String configPath = TEST_LDAP_USERS_LDIF;
+ server.importFromLDIF(true, configPath);
+ server.startListening();
+ }
+
+ @AfterAll
+ static void shutDownDirectoryServer () {
+ server.shutDown(true);
+ }
+
+ @Test
+ public void loginWithExtraProfileNameWorks () throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("testuser123", "password", TEST_LDAP_CONF));
+ }
+
+ @Test
+ public void loginWithUidWorks () throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("testuser", "password", TEST_LDAP_CONF));
+ }
+
+ @Test
+ public void loginWithUidAndBase64PasswordWorks () throws LDAPException {
+ final byte[] passwordBytes = StaticUtils.getBytes("password");
+ String pw = Base64.encode(passwordBytes);
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("testuser", pw, TEST_LDAP_CONF));
+ }
+
+ @Test
+ public void loginWithEmailWorks () throws LDAPException {
+ final byte[] passwordBytes = StaticUtils.getBytes("password");
+ String pw = Base64.encode(passwordBytes);
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("testuser@example.com", pw, TEST_LDAP_CONF));
+ }
+
+ @Test
+ public void allLoginPasswordCombinationsWork () throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("uid", "userPassword", TEST_LDAP_CONF));
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("uid", "extraPassword", TEST_LDAP_CONF));
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("mail@example.org",
+ "userPassword", TEST_LDAP_CONF));
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("mail@example.org",
+ "extraPassword", TEST_LDAP_CONF));
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("extraProfile",
+ "userPassword", TEST_LDAP_CONF));
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("extraProfile",
+ "extraPassword", TEST_LDAP_CONF));
+ }
+
+ @Test
+ public void loginWithWrongEmailFails () throws LDAPException {
+ assertEquals(LDAP_AUTH_RUNKNOWN, LdapAuth3
+ .login("notestuser@example.com", "topsecret", TEST_LDAP_CONF));
+ }
+
+ @Test
+ public void loginWithEmailAndWrongPasswordFails () throws LDAPException {
+ assertEquals(LDAP_AUTH_RUNKNOWN, LdapAuth3.login("testuser@example.com",
+ "wrongpw", TEST_LDAP_CONF));
+ }
+
+ @Test
+ public void loginWithUsernameAndWrongPasswordFails () throws LDAPException {
+ assertEquals(LDAP_AUTH_RUNKNOWN,
+ LdapAuth3.login("testuser", "wrongpw", TEST_LDAP_CONF));
+ }
+
+ @Test
+ public void loginOfNotRegisteredUserFails () throws LDAPException {
+ assertEquals(LDAP_AUTH_RNOTREG, LdapAuth3.login("not_registered_user",
+ "topsecret", TEST_LDAP_CONF));
+ }
+
+ @Test
+ public void blockedUserIsRefused () throws LDAPException {
+ assertEquals(LDAP_AUTH_RLOCKED, LdapAuth3.login("nameOfBlockedUser",
+ "topsecret", TEST_LDAP_CONF));
+ }
+
+ @Test
+ public void loginWithUsernameOverSSLWorks () throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("testuser", "password", TEST_LDAPS_CONF));
+ }
+
+ @Test
+ public void loginOnTrustedServerWorks () throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("testuser", "password", TEST_LDAPS_TS_CONF));
+ }
+
+ @Test
+ public void loginOnTrustedServerWithWrongPassswordFails ()
+ throws LDAPException {
+ assertEquals(LDAP_AUTH_RUNKNOWN,
+ LdapAuth3.login("testuser", "topsecrets", TEST_LDAPS_TS_CONF));
+ }
+
+ @Test
+ public void passwordWithAsteriskWorks () throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("test", "top*ecret", TEST_LDAPS_CONF));
+ }
+
+ @Test
+ public void passwordWithGlobOperatorFails () throws LDAPException {
+ assertEquals(LDAP_AUTH_RUNKNOWN,
+ LdapAuth3.login("testuser", "passw*", TEST_LDAPS_TS_CONF));
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("testuser", "password", TEST_LDAPS_TS_CONF));
+ }
+
+ @Test
+ public void passwordWithExistenceOperatorFails () throws LDAPException {
+ assertEquals(LDAP_AUTH_RUNKNOWN,
+ LdapAuth3.login("testuser", "*", TEST_LDAPS_TS_CONF));
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("testuser", "password", TEST_LDAPS_TS_CONF));
+ }
+
+ @Test
+ public void gettingMailAttributeForUid () throws LDAPException {
+ assertEquals(LdapAuth3.getEmail("testuser", TEST_LDAP_CONF),
+ "testuser@example.com");
+ assertEquals(LdapAuth3.getEmail("testuser2", TEST_LDAPS_CONF),
+ "peter@example.org");
+ assertEquals(null, LdapAuth3.getEmail("non-exsting", TEST_LDAPS_CONF));
+ }
+
+ @Test
+ public void gettingUsernameForEmail () throws LDAPException {
+ assertEquals(
+ LdapAuth3.getUsername("testuser@example.com", TEST_LDAP_CONF),
+ "idsTestUser");
+ assertEquals(
+ LdapAuth3.getUsername("peter@example.org", TEST_LDAPS_CONF),
+ "testuser2");
+ assertEquals(null,
+ LdapAuth3.getUsername("non-exsting", TEST_LDAPS_CONF));
+ assertEquals(LdapAuth3.getUsername("testUser2", TEST_LDAPS_CONF),
+ "testuser2");
+ // login with uid, get idsC2Profile username
+ assertEquals(LdapAuth3.getUsername("testUser", TEST_LDAPS_CONF),
+ "idsTestUser");
+ }
+
+ @Test
+ public void gettingMailAttributeForNotRegisteredUserWorks ()
+ throws LDAPException {
+ assertEquals(LdapAuth3.getEmail("not_registered_user", TEST_LDAP_CONF),
+ "not_registered_user@example.com");
+ }
+
+ @Test
+ public void gettingMailAttributeForBlockedUserWorks ()
+ throws LDAPException {
+ assertEquals(LdapAuth3.getEmail("nameOfBlockedUser", TEST_LDAP_CONF),
+ "nameOfBlockedUser@example.com");
+ }
+
+ @Test
+ public void canLoadLdapConfig () {
+ LDAPConfig ldapConfig = new LDAPConfig(TEST_LDAPS_CONF);
+ assertEquals(3269, ldapConfig.port);
+ assertEquals(ldapConfig.host, "localhost");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/authentication/LdapOAuth2Test.java b/src/test/java/de/ids_mannheim/korap/authentication/LdapOAuth2Test.java
new file mode 100644
index 0000000..c45cc37
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/authentication/LdapOAuth2Test.java
@@ -0,0 +1,176 @@
+package de.ids_mannheim.korap.authentication;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.net.URI;
+import java.security.GeneralSecurityException;
+
+import org.apache.http.entity.ContentType;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import com.unboundid.ldap.sdk.LDAPException;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.constant.AuthenticationMethod;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
+import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
+import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
+import de.ids_mannheim.korap.oauth2.entity.AccessToken;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.controller.OAuth2TestBase;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+public class LdapOAuth2Test extends OAuth2TestBase {
+
+ @Autowired
+ private FullConfiguration config;
+
+ @Autowired
+ private AccessTokenDao accessDao;
+
+ @Autowired
+ private RefreshTokenDao refreshDao;
+
+ @Autowired
+ private OAuth2ClientDao clientDao;
+
+ private String testUsername = "idsTestUser";
+
+ private String testUserEmail = "testuser@example.com";
+
+ private String redirectUri = "https://client.com/redirect";
+
+ @BeforeAll
+ static void startTestLDAPServer ()
+ throws LDAPException, GeneralSecurityException {
+ LdapAuth3Test.startDirectoryServer();
+ }
+
+ @AfterAll
+ static void stopTestLDAPServer () {
+ LdapAuth3Test.shutDownDirectoryServer();
+ }
+
+ @BeforeEach
+ public void setLDAPAuthentication () {
+ config.setOAuth2passwordAuthentication(AuthenticationMethod.LDAP);
+ }
+
+ @AfterEach
+ public void resetAuthenticationMethod () {
+ config.setOAuth2passwordAuthentication(AuthenticationMethod.TEST);
+ }
+
+ @Test
+ public void testRequestTokenPasswordUnknownUser ()
+ throws KustvaktException {
+ Response response = requestTokenWithPassword(superClientId,
+ clientSecret, "unknown", "password");
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2023, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "LDAP Authentication failed due to unknown user or password!");
+ }
+
+ @Test
+ public void testMapEmailToUsername () throws KustvaktException {
+ Response response = requestTokenWithPassword(superClientId,
+ clientSecret, testUserEmail, "password");
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String accessToken = node.at("/access_token").asText();
+ AccessToken accessTokenObj = accessDao.retrieveAccessToken(accessToken);
+ assertEquals(testUsername, accessTokenObj.getUserId());
+ String refreshToken = node.at("/refresh_token").asText();
+ RefreshToken rt = refreshDao.retrieveRefreshToken(refreshToken);
+ assertEquals(testUsername, rt.getUserId());
+ testRegisterPublicClient(accessToken);
+ node = testRegisterConfidentialClient(accessToken);
+ String clientId = node.at("/client_id").asText();
+ String clientSecret = node.at("/client_secret").asText();
+ testRequestTokenWithAuthorization(clientId, clientSecret, accessToken);
+ }
+
+ private void testRegisterPublicClient (String accessToken)
+ throws KustvaktException {
+ OAuth2ClientJson json = new OAuth2ClientJson();
+ json.setName("LDAP test client");
+ json.setType(OAuth2ClientType.PUBLIC);
+ json.setDescription(
+ "Test registering a public client with LDAP authentication");
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("register").request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .post(Entity.json(json));
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String clientId = node.at("/client_id").asText();
+ OAuth2Client client = clientDao.retrieveClientById(clientId);
+ assertEquals(testUsername, client.getRegisteredBy());
+ }
+
+ private JsonNode testRegisterConfidentialClient (String accessToken)
+ throws KustvaktException {
+ OAuth2ClientJson json = new OAuth2ClientJson();
+ json.setName("LDAP test client");
+ json.setType(OAuth2ClientType.CONFIDENTIAL);
+ json.setRedirectURI(redirectUri);
+ json.setDescription(
+ "Test registering a confidential client with LDAP authentication");
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("register").request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .post(Entity.json(json));
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String clientId = node.at("/client_id").asText();
+ OAuth2Client client = clientDao.retrieveClientById(clientId);
+ assertEquals(testUsername, client.getRegisteredBy());
+ return node;
+ }
+
+ private void testRequestTokenWithAuthorization (String clientId,
+ String clientSecret, String accessToken) throws KustvaktException {
+ String authHeader = "Bearer " + accessToken;
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("authorize").queryParam("response_type", "code")
+ .queryParam("client_id", clientId)
+ .queryParam("client_secret", clientSecret)
+ .queryParam("scope", "search match_info").request()
+ .header(Attributes.AUTHORIZATION, authHeader).get();
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+ URI redirectUri = response.getLocation();
+ MultiValueMap<String, String> params = UriComponentsBuilder
+ .fromUri(redirectUri).build().getQueryParams();
+ String code = params.getFirst("code");
+ response = requestTokenWithAuthorizationCodeAndForm(clientId,
+ clientSecret, code);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String at = node.at("/access_token").asText();
+ AccessToken accessTokenObj = accessDao.retrieveAccessToken(at);
+ assertEquals(testUsername, accessTokenObj.getUserId());
+ String refreshToken = node.at("/refresh_token").asText();
+ RefreshToken rt = refreshDao.retrieveRefreshToken(refreshToken);
+ assertEquals(testUsername, rt.getUserId());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/authentication/RandomCodeGeneratorTest.java b/src/test/java/de/ids_mannheim/korap/authentication/RandomCodeGeneratorTest.java
new file mode 100644
index 0000000..9ffe776
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/authentication/RandomCodeGeneratorTest.java
@@ -0,0 +1,48 @@
+package de.ids_mannheim.korap.authentication;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.security.NoSuchAlgorithmException;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.encryption.RandomCodeGenerator;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+
+public class RandomCodeGeneratorTest extends SpringJerseyTest {
+
+ @Autowired
+ private RandomCodeGenerator random;
+
+ @Test
+ public void testRandomGenerator ()
+ throws NoSuchAlgorithmException, KustvaktException {
+ String value = random.createRandomCode();
+ assertEquals(22, value.length());
+ // System.out.println(value);
+ }
+
+ @Disabled
+ public void testRandomGeneratorPerformance ()
+ throws NoSuchAlgorithmException, KustvaktException {
+ long min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
+ String code;
+ while (true) {
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < 10000; i++) {
+ code = random.createRandomCode();
+ code = random.filterRandomCode(code);
+ }
+ long end = System.currentTimeMillis();
+ long duration = end - start;
+ if (duration < min)
+ min = duration;
+ else if (duration > max)
+ max = duration;
+ System.out.println(
+ "d : " + duration + " min :" + min + ", max: " + max);
+ }
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/cache/NamedVCLoaderTest.java b/src/test/java/de/ids_mannheim/korap/cache/NamedVCLoaderTest.java
new file mode 100644
index 0000000..7b7db2f
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/cache/NamedVCLoaderTest.java
@@ -0,0 +1,45 @@
+package de.ids_mannheim.korap.cache;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import de.ids_mannheim.korap.collection.DocBits;
+import de.ids_mannheim.korap.config.NamedVCLoader;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.dao.QueryDao;
+import de.ids_mannheim.korap.entity.QueryDO;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.util.QueryException;
+
+public class NamedVCLoaderTest extends SpringJerseyTest {
+
+ @Autowired
+ private NamedVCLoader vcLoader;
+
+ @Autowired
+ private QueryDao dao;
+
+ @Test
+ public void testNamedVCLoader ()
+ throws IOException, QueryException, KustvaktException {
+ String vcId = "named-vc1";
+ vcLoader.loadVCToCache(vcId, "/vc/named-vc1.jsonld");
+ assertTrue(VirtualCorpusCache.contains(vcId));
+ Map<String, DocBits> cachedData = VirtualCorpusCache.retrieve(vcId);
+ assertTrue(cachedData.size() > 0);
+ VirtualCorpusCache.delete(vcId);
+ assertFalse(VirtualCorpusCache.contains(vcId));
+ QueryDO vc = dao.retrieveQueryByName(vcId, "system");
+ assertNotNull(vc);
+ dao.deleteQuery(vc);
+ vc = dao.retrieveQueryByName(vcId, "system");
+ assertNull(vc);
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/cache/TotalResultTest.java b/src/test/java/de/ids_mannheim/korap/cache/TotalResultTest.java
new file mode 100644
index 0000000..819f7a3
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/cache/TotalResultTest.java
@@ -0,0 +1,111 @@
+package de.ids_mannheim.korap.cache;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.core.service.SearchService;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+public class TotalResultTest extends SpringJerseyTest {
+
+ @Autowired
+ private SearchService searchService;
+
+ @Test
+ public void testClearCache () {
+ for (int i = 1; i < 10; i++) {
+ searchService.getTotalResultCache().storeInCache(i, "10");
+ }
+
+ searchService.getTotalResultCache().clearCache();
+
+ assertEquals(0, searchService.getTotalResultCache()
+ .getAllCacheElements().size());
+ }
+
+ @Test
+ public void testSearchWithPaging () throws KustvaktException {
+ searchService.getTotalResultCache().clearCache();
+
+ assertEquals(0, searchService.getTotalResultCache()
+ .getAllCacheElements().size());
+
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .queryParam("page", "1").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertTrue(node.at("/meta/totalResults").isNumber(),
+ "totalResults should be a number");
+ int totalResults = node.at("/meta/totalResults").asInt();
+ assertEquals(1, searchService.getTotalResultCache()
+ .getAllCacheElements().size());
+ response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .queryParam("page", "2").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ entity = response.readEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ assertTrue(node.at("/meta/totalResults").isNumber(),
+ "totalResults should be a number");
+ assertEquals(totalResults, node.at("/meta/totalResults").asInt());
+ assertEquals(1, searchService.getTotalResultCache()
+ .getAllCacheElements().size());
+ assertTrue(node.at("/meta/cutOff").isMissingNode());
+ testSearchWithCutOff();
+ }
+
+ @Test
+ public void testSearchWithCutOffTrue () throws KustvaktException {
+ int cacheSize = searchService.getTotalResultCache()
+ .getAllCacheElements().size();
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "ich").queryParam("ql", "poliqarp")
+ .queryParam("page", "2").queryParam("cutoff", "true").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = "{\"meta\":{\"startPage\":2,\"tokens\":false,\"cutOff\":"
+ + "true,\"snippets\":true,\"timeout\":10000},\"query\":{\"@type\":"
+ + "\"koral:token\",\"wrap\":{\"@type\":\"koral:term\",\"match\":"
+ + "\"match:eq\",\"layer\":\"orth\",\"key\":\"ich\",\"foundry\":"
+ + "\"opennlp\",\"rewrites\":[{\"@type\":\"koral:rewrite\",\"src\":"
+ + "\"Kustvakt\",\"operation\":\"operation:injection\",\"scope\":"
+ + "\"foundry\"}]}},\"@context\":\"http://korap.ids-mannheim.de/ns"
+ + "/koral/0.3/context.jsonld\",\"collection\":{\"@type\":\"koral:"
+ + "doc\",\"match\":\"match:eq\",\"type\":\"type:regex\",\"value\":"
+ + "\"CC-BY.*\",\"key\":\"availability\",\"rewrites\":[{\"@type\":"
+ + "\"koral:rewrite\",\"src\":\"Kustvakt\",\"operation\":\"operation:"
+ + "insertion\",\"scope\":\"availability(FREE)\"}]}}";
+ int cacheKey = searchService.createTotalResultCacheKey(query);
+ assertEquals(null,
+ searchService.getTotalResultCache().getCacheValue(cacheKey));
+ assertEquals(cacheSize, searchService.getTotalResultCache()
+ .getAllCacheElements().size());
+ }
+
+ private void testSearchWithCutOff () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .queryParam("page", "3").queryParam("cutoff", "false").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertTrue(node.at("/meta/cutOff").isMissingNode());
+ response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .queryParam("page", "4").queryParam("cutoff", "true").request()
+ .get();
+ entity = response.readEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ assertTrue(node.at("/meta/cutOff").asBoolean());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/config/LiteJerseyTest.java b/src/test/java/de/ids_mannheim/korap/config/LiteJerseyTest.java
new file mode 100644
index 0000000..80b00a5
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/config/LiteJerseyTest.java
@@ -0,0 +1,58 @@
+package de.ids_mannheim.korap.config;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.ServletDeploymentContext;
+import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
+import org.glassfish.jersey.test.spi.TestContainerException;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration("classpath:test-config-lite.xml")
+public abstract class LiteJerseyTest extends JerseyTest {
+
+ public static final String API_VERSION = "v1.0";
+
+ @Autowired
+ protected GenericApplicationContext applicationContext;
+
+ public static String[] classPackages = new String[] {
+ "de.ids_mannheim.korap.core.web",
+ "de.ids_mannheim.korap.web.filter",
+ "de.ids_mannheim.korap.web.utils", "de.ids_mannheim.korap.test",
+ "com.fasterxml.jackson.jaxrs.json" };
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory ()
+ throws TestContainerException {
+ return new GrizzlyWebTestContainerFactory();
+ }
+
+ @BeforeEach
+ @Override
+ public void setUp () throws Exception {
+ GenericWebApplicationContext genericContext = new GenericWebApplicationContext();
+ genericContext.setParent(this.applicationContext);
+ genericContext.setClassLoader(this.applicationContext.getClassLoader());
+ StaticContextLoaderListener.applicationContext = genericContext;
+ super.setUp();
+ }
+
+ @Override
+ protected DeploymentContext configureDeployment () {
+ return ServletDeploymentContext
+ .forServlet(new ServletContainer(
+ new ResourceConfig().packages(classPackages)))
+ .addListener(StaticContextLoaderListener.class)
+ .contextParam("adminToken", "secret").build();
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java b/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java
new file mode 100644
index 0000000..a1baccb
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java
@@ -0,0 +1,59 @@
+package de.ids_mannheim.korap.config;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.ServletDeploymentContext;
+import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
+import org.glassfish.jersey.test.spi.TestContainerException;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+
+@DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration("classpath:test-config.xml")
+public abstract class SpringJerseyTest extends JerseyTest {
+
+ public final static String API_VERSION = "v1.0";
+
+ @Autowired
+ protected GenericApplicationContext applicationContext;
+
+ public static String[] classPackages = new String[] {
+ "de.ids_mannheim.korap.web", "de.ids_mannheim.korap.core.web",
+ "de.ids_mannheim.korap.test", "com.fasterxml.jackson.jaxrs.json" };
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory ()
+ throws TestContainerException {
+ return new GrizzlyWebTestContainerFactory();
+ }
+
+ @BeforeEach
+ @Override
+ public void setUp () throws Exception {
+ GenericWebApplicationContext genericContext = new GenericWebApplicationContext();
+ genericContext.setParent(this.applicationContext);
+ genericContext.setClassLoader(this.applicationContext.getClassLoader());
+ StaticContextLoaderListener.applicationContext = genericContext;
+ super.setUp();
+ }
+
+ @Override
+ protected DeploymentContext configureDeployment () {
+ return ServletDeploymentContext
+ .forServlet(new ServletContainer(
+ new ResourceConfig().packages(classPackages)))
+ .addListener(StaticContextLoaderListener.class)
+ .contextParam("adminToken", "secret").build();
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/config/StaticContextLoaderListener.java b/src/test/java/de/ids_mannheim/korap/config/StaticContextLoaderListener.java
new file mode 100644
index 0000000..018bd6e
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/config/StaticContextLoaderListener.java
@@ -0,0 +1,46 @@
+package de.ids_mannheim.korap.config;
+
+import org.springframework.web.context.ContextLoaderListener;
+import org.springframework.web.context.WebApplicationContext;
+
+import jakarta.servlet.ServletContextEvent;
+
+/**
+ * A hack to inject the application context generated by
+ * SpringJUnit4ClassRunner in the test suite.
+ *
+ * @author margaretha
+ *
+ */
+public class StaticContextLoaderListener extends ContextLoaderListener {
+
+ public static WebApplicationContext applicationContext;
+
+ private ClassLoader contextClassLoader;
+
+ public StaticContextLoaderListener () {
+ super(applicationContext);
+ }
+
+ @Override
+ public void contextInitialized (ServletContextEvent event) {
+ contextClassLoader = Thread.currentThread().getContextClassLoader();
+ super.contextInitialized(event);
+ }
+
+ @Override
+ public void contextDestroyed (ServletContextEvent event) {
+ // Perform the destruction with the same contextual ClassLoader that was present
+ // during initialization.
+ // This a workaround for a bug in org.glassfish.grizzly.servlet.WebappContext
+ // that causes a memory leak in org.springframework.web.context.ContextLoader.
+ // This logic should be moved to WebappContext.contextDestroyed(). Until this
+ // is fixed in Grizzly; This is a good solution.
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+
+ super.contextDestroyed(event);
+
+ Thread.currentThread().setContextClassLoader(loader);
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/config/TestVariables.java b/src/test/java/de/ids_mannheim/korap/config/TestVariables.java
new file mode 100644
index 0000000..e4658ce
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/config/TestVariables.java
@@ -0,0 +1,355 @@
+package de.ids_mannheim.korap.config;
+
+/**
+ * Created by hanl on 30.05.16.
+ */
+public class TestVariables {
+
+ public static final String SIMPLE_ADD_QUERY = "[pos=ADJA]";
+
+ public static final String RESULT = "{\n"
+ + "\t\"@context\": \"http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld\",\n"
+ + "\t\"meta\": {\n" + "\t\t\"count\": 25,\n"
+ + "\t\t\"startIndex\": 0,\n" + "\t\t\"timeout\": 120000,\n"
+ + "\t\t\"context\": {\n" + "\t\t\t\"left\": [\"token\", 6],\n"
+ + "\t\t\t\"right\": [\"token\", 6]\n" + "\t\t},\n"
+ + "\t\t\"fields\": [\"pubDate\", \"subTitle\", \"author\", \"pubPlace\", \"title\", \"textSigle\", \"UID\", \"ID\", \"layerInfos\", \"corpusSigle\", \"docSigle\", \"corpusID\", \"textClass\"],\n"
+ + "\t\t\"version\": \"0.55.5\",\n"
+ + "\t\t\"benchmark\": \"0.117436617 s\",\n"
+ + "\t\t\"totalResults\": 31,\n"
+ + "\t\t\"serialQuery\": \"tokens:s:das\",\n"
+ + "\t\t\"itemsPerPage\": 25\n" + "\t},\n" + "\t\"query\": {\n"
+ + "\t\t\"@type\": \"koral:token\",\n" + "\t\t\"wrap\": {\n"
+ + "\t\t\t\"@type\": \"koral:term\",\n" + "\t\t\t\"key\": \"das\",\n"
+ + "\t\t\t\"layer\": \"orth\",\n"
+ + "\t\t\t\"match\": \"match:eq\",\n"
+ + "\t\t\t\"foundry\": \"opennlp\",\n" + "\t\t\t\"rewrites\": [{\n"
+ + "\t\t\t\t\"@type\": \"koral:rewrite\",\n"
+ + "\t\t\t\t\"src\": \"Kustvakt\",\n"
+ + "\t\t\t\t\"operation\": \"operation:injection\",\n"
+ + "\t\t\t\t\"scope\": \"foundry\"\n" + "\t\t\t}]\n" + "\t\t},\n"
+ + "\t\t\"idn\": \"das_0\",\n" + "\t\t\"rewrites\": [{\n"
+ + "\t\t\t\"@type\": \"koral:rewrite\",\n"
+ + "\t\t\t\"src\": \"Kustvakt\",\n"
+ + "\t\t\t\"operation\": \"operation:injection\",\n"
+ + "\t\t\t\"scope\": \"idn\"\n" + "\t\t}]\n" + "\t},\n"
+ + "\t\"collection\": {\n" + "\t\t\"@type\": \"koral:docGroup\",\n"
+ + "\t\t\"operation\": \"operation:or\",\n"
+ + "\t\t\"operands\": [{\n" + "\t\t\t\"@type\": \"koral:doc\",\n"
+ + "\t\t\t\"key\": \"corpusSigle\",\n"
+ + "\t\t\t\"value\": \"GOE\",\n" + "\t\t\t\"match\": \"match:eq\"\n"
+ + "\t\t}, {\n" + "\t\t\t\"@type\": \"koral:doc\",\n"
+ + "\t\t\t\"key\": \"corpusSigle\",\n"
+ + "\t\t\t\"value\": \"WPD\",\n" + "\t\t\t\"match\": \"match:eq\"\n"
+ + "\t\t}],\n" + "\t\t\"rewrites\": [{\n"
+ + "\t\t\t\"@type\": \"koral:rewrite\",\n"
+ + "\t\t\t\"src\": \"Kustvakt\",\n"
+ + "\t\t\t\"operation\": \"operation:insertion\",\n"
+ + "\t\t\t\"scope\": \"corpusSigle\"\n" + "\t\t}]\n" + "\t},\n"
+ + "\t\"matches\": [{\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00004\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n"
+ + "\t\t\"title\": \"A (Logik)\",\n"
+ + "\t\t\"author\": \"Zenogantner; ElRaki; 1\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>z.B. mit dem Wort Barbara" bezeichnet, </span><mark>das</mark><span class=\\\"context-right\\\"> dreimal den Buchstaben a enthält, aber<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00004-p195-196\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"kultur musik freizeit-unterhaltung reisen\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.02439\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n"
+ + "\t\t\"title\": \"Aegukka\",\n"
+ + "\t\t\"author\": \"ThorstenS; 2\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>wörtlich "Das Lied der Liebe für </span><mark>das</mark><span class=\\\"context-right\\\"> Land", oder "Das patriotische Lied". Es<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.02439-p22-23\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"kultur musik freizeit-unterhaltung reisen\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.02439\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n"
+ + "\t\t\"title\": \"Aegukka\",\n"
+ + "\t\t\"author\": \"ThorstenS; 2\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>Besatzungszeit von 1910 bis 1945 wurde </span><mark>das</mark><span class=\\\"context-right\\\"> Lied verboten. Im Ausland lebende Koreaner<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.02439-p74-75\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"kultur musik freizeit-unterhaltung reisen\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.02439\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n"
+ + "\t\t\"title\": \"Aegukka\",\n"
+ + "\t\t\"author\": \"ThorstenS; 2\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>3. Deutsche Übersetzung 1. Strophe Bis </span><mark>das</mark><span class=\\\"context-right\\\"> Meer des Ostens ausdörrt und der<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.02439-p298-299\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>Ausnahme von Fremdwörtern und Namen ist </span><mark>das</mark><span class=\\\"context-right\\\"> A der einzige Buchstabe im Deutschen<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p41-42\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>flache Stellung niedergedrückt wird. Stellt man </span><mark>das</mark><span class=\\\"context-right\\\"> Verhältnis der drei Hauptvokale a, i<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p107-108\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>kommt i als der hellste an </span><mark>das</mark><span class=\\\"context-right\\\"> obere, u als der dumpfeste an<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p127-128\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>obere, u als der dumpfeste an </span><mark>das</mark><span class=\\\"context-right\\\"> untere Ende, a als der mittlere<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p134-135\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>im 9. Jahrhundert v. Chr. war </span><mark>das</mark><span class=\\\"context-right\\\"> Schriftzeichen bereits stark stilisiert, die Hörner<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p271-272\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>im Alphabet inne. Als die Griechen </span><mark>das</mark><span class=\\\"context-right\\\"> Phönizische Alphabet übernamen, drehten sie das<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p313-314\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>das Phönizische Alphabet übernamen, drehten sie </span><mark>das</mark><span class=\\\"context-right\\\"> Zeichen um 90 Grad und machten<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p319-320\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>um 90 Grad und machten daraus </span><mark>das</mark><span class=\\\"context-right\\\"> Alpha. Da das Griechische reich an<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p327-328\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>und machten daraus das Alpha. Da </span><mark>das</mark><span class=\\\"context-right\\\"> Griechische reich an Vokalen war, verwendeten<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p330-331\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>reich an Vokalen war, verwendeten sie </span><mark>das</mark><span class=\\\"context-right\\\"> Zeichen für den Lautwert a. Die<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p338-339\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>den Lautwert a. Die Etrusker übernahmen </span><mark>das</mark><span class=\\\"context-right\\\"> frühgriechische Alpha und ließen es größtenteils<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p347-348\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>von rechts nach links) versahen sie </span><mark>das</mark><span class=\\\"context-right\\\"> Zeichen mit einem Abschwung nach links<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p365-366\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>Abschwung nach links. Als die Römer </span><mark>das</mark><span class=\\\"context-right\\\"> lateinische Alphabet schufen, verwendeten sie das<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p375-376\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>das lateinische Alphabet schufen, verwendeten sie </span><mark>das</mark><span class=\\\"context-right\\\"> A aus dem etruskischen Alphabet, der<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p381-382\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>in der Biologie steht A für </span><mark>das</mark><span class=\\\"context-right\\\"> Nukleosid Adenosin steht A die Base<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p404-405\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>Wert 10, siehe auch Hexadezimalsystem. steht </span><mark>das</mark><span class=\\\"context-right\\\"> Symbol ∀ (ein auf den Kopf gestelltes<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p526-527\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>für die Vorsilbe Atto ist A </span><mark>das</mark><span class=\\\"context-right\\\"> Einheitensymbol der elektrischen Stromstärke Ampere in<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p624-625\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>Anordnung (Rechtswesen), Agent (Börse), Aufzeichung (Rechtsw.), </span><mark>das</mark><span class=\\\"context-right\\\"> Flächenmaß Acre und Ausgabe A ist<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p757-758\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>Band). in Redewendungen für den Anfang (</span><mark>das</mark><span class=\\\"context-right\\\"> A und O, von A bis<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p777-778\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>Z). a ist die Abkürzung für </span><mark>das</mark><span class=\\\"context-right\\\"> Flächenmaß Ar in der Kaufmannssprache bedeutet<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p790-791\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}, {\n" + "\t\t\"field\": \"tokens\",\n"
+ + "\t\t\"textClass\": \"freizeit-unterhaltung reisen wissenschaft populaerwissenschaft\",\n"
+ + "\t\t\"pubPlace\": \"URL:http://de.wikipedia.org\",\n"
+ + "\t\t\"textSigle\": \"WPD_AAA.00001\",\n"
+ + "\t\t\"docSigle\": \"WPD_AAA\",\n"
+ + "\t\t\"corpusSigle\": \"WPD\",\n" + "\t\t\"title\": \"A\",\n"
+ + "\t\t\"author\": \"Ruru; Jens.Ol; Aglarech; u.a.\",\n"
+ + "\t\t\"layerInfos\": \"base/s=spans cnx/c=spans cnx/l=tokens cnx/m=tokens cnx/p=tokens cnx/s=spans cnx/syn=tokens corenlp/s=spans mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans xip/l=tokens xip/p=tokens xip/s=spans\",\n"
+ + "\t\t\"startMore\": true,\n" + "\t\t\"endMore\": true,\n"
+ + "\t\t\"snippet\": \"<span class=\\\"context-left\\\"><span class=\\\"more\\\"></span>ad zu) ‚[das Stück] zu...‘ für </span><mark>das</mark><span class=\\\"context-right\\\"> Französische à „nach“, z. B. in<span class=\\\"more\\\"></span></span>\",\n"
+ + "\t\t\"matchID\": \"match-WPD_AAA.00001-p805-806\",\n"
+ + "\t\t\"UID\": 0,\n" + "\t\t\"pubDate\": \"2005-03-28\"\n"
+ + "\t}]\n" + "}";
+
+}
diff --git a/src/test/java/de/ids_mannheim/korap/dao/RolePrivilegeDaoTest.java b/src/test/java/de/ids_mannheim/korap/dao/RolePrivilegeDaoTest.java
new file mode 100644
index 0000000..13b2c91
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/dao/RolePrivilegeDaoTest.java
@@ -0,0 +1,78 @@
+package de.ids_mannheim.korap.dao;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import de.ids_mannheim.korap.constant.PredefinedRole;
+import de.ids_mannheim.korap.constant.PrivilegeType;
+import de.ids_mannheim.korap.entity.Privilege;
+import de.ids_mannheim.korap.entity.Role;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration("classpath:test-config.xml")
+public class RolePrivilegeDaoTest {
+
+ @Autowired
+ private RoleDao roleDao;
+
+ @Autowired
+ private PrivilegeDao privilegeDao;
+
+ @Test
+ public void retrievePredefinedRole () {
+ Role r = roleDao
+ .retrieveRoleById(PredefinedRole.USER_GROUP_ADMIN.getId());
+ assertEquals(1, r.getId());
+ }
+
+ @Test
+ public void createDeleteRole () {
+ String roleName = "vc editor";
+ List<PrivilegeType> privileges = new ArrayList<PrivilegeType>();
+ privileges.add(PrivilegeType.READ);
+ privileges.add(PrivilegeType.WRITE);
+ roleDao.createRole(roleName, privileges);
+ Role r = roleDao.retrieveRoleByName(roleName);
+ assertEquals(roleName, r.getName());
+ assertEquals(2, r.getPrivileges().size());
+ roleDao.deleteRole(r.getId());
+ }
+
+ @Test
+ public void updateRole () {
+ Role role = roleDao.retrieveRoleByName("USER_GROUP_MEMBER");
+ roleDao.editRoleName(role.getId(), "USER_GROUP_MEMBER role");
+ role = roleDao.retrieveRoleById(role.getId());
+ assertEquals(role.getName(), "USER_GROUP_MEMBER role");
+ roleDao.editRoleName(role.getId(), "USER_GROUP_MEMBER");
+ role = roleDao.retrieveRoleById(role.getId());
+ assertEquals(role.getName(), "USER_GROUP_MEMBER");
+ }
+
+ @Test
+ public void addDeletePrivilegeOfExistingRole () {
+ Role role = roleDao.retrieveRoleByName("USER_GROUP_MEMBER");
+ List<Privilege> privileges = role.getPrivileges();
+ assertEquals(1, role.getPrivileges().size());
+ assertEquals(privileges.get(0).getName(), PrivilegeType.DELETE);
+ // add privilege
+ List<PrivilegeType> privilegeTypes = new ArrayList<PrivilegeType>();
+ privilegeTypes.add(PrivilegeType.READ);
+ privilegeDao.addPrivilegesToRole(role, privilegeTypes);
+ role = roleDao.retrieveRoleByName("USER_GROUP_MEMBER");
+ assertEquals(2, role.getPrivileges().size());
+ // delete privilege
+ privilegeDao.deletePrivilegeFromRole(role.getId(), PrivilegeType.READ);
+ role = roleDao.retrieveRoleByName("USER_GROUP_MEMBER");
+ assertEquals(1, role.getPrivileges().size());
+ assertEquals(privileges.get(0).getName(), PrivilegeType.DELETE);
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/dao/UserGroupDaoTest.java b/src/test/java/de/ids_mannheim/korap/dao/UserGroupDaoTest.java
new file mode 100644
index 0000000..1ee65bf
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/dao/UserGroupDaoTest.java
@@ -0,0 +1,145 @@
+package de.ids_mannheim.korap.dao;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.constant.GroupMemberStatus;
+import de.ids_mannheim.korap.constant.PredefinedRole;
+import de.ids_mannheim.korap.constant.QueryAccessStatus;
+import de.ids_mannheim.korap.constant.QueryType;
+import de.ids_mannheim.korap.constant.ResourceType;
+import de.ids_mannheim.korap.constant.UserGroupStatus;
+import de.ids_mannheim.korap.entity.QueryDO;
+import de.ids_mannheim.korap.entity.Role;
+import de.ids_mannheim.korap.entity.UserGroup;
+import de.ids_mannheim.korap.entity.UserGroupMember;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.user.User.CorpusAccess;
+import edu.emory.mathcs.backport.java.util.Collections;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration("classpath:test-config.xml")
+public class UserGroupDaoTest {
+
+ @Autowired
+ private UserGroupDao userGroupDao;
+
+ @Autowired
+ private QueryDao virtualCorpusDao;
+
+ @Autowired
+ private RoleDao roleDao;
+
+ @Autowired
+ private FullConfiguration config;
+
+ @Test
+ public void createDeleteNewUserGroup () throws KustvaktException {
+ String groupName = "test group";
+ String createdBy = "test class";
+ // create group
+ int groupId = userGroupDao.createGroup(groupName, null, createdBy,
+ UserGroupStatus.ACTIVE);
+ // retrieve group
+ UserGroup group = userGroupDao.retrieveGroupById(groupId, true);
+ assertEquals(groupName, group.getName());
+ assertEquals(createdBy, group.getCreatedBy());
+ assertEquals(UserGroupStatus.ACTIVE, group.getStatus());
+ assertNull(group.getDeletedBy());
+ // group member
+ List<UserGroupMember> members = group.getMembers();
+ assertEquals(1, members.size());
+ UserGroupMember m = members.get(0);
+ assertEquals(GroupMemberStatus.ACTIVE, m.getStatus());
+ assertEquals(createdBy, m.getCreatedBy());
+ assertEquals(createdBy, m.getUserId());
+ // member roles
+ Set<Role> roles = roleDao.retrieveRoleByGroupMemberId(m.getId());
+ assertEquals(2, roles.size());
+ ArrayList<Role> roleList = new ArrayList<>(2);
+ roleList.addAll(roles);
+ Collections.sort(roleList);
+ assertEquals(PredefinedRole.USER_GROUP_ADMIN.getId(),
+ roleList.get(0).getId());
+ assertEquals(PredefinedRole.VC_ACCESS_ADMIN.getId(),
+ roleList.get(1).getId());
+ // retrieve VC by group
+ List<QueryDO> vc = virtualCorpusDao.retrieveQueryByGroup(groupId);
+ assertEquals(0, vc.size());
+ // soft delete group
+ userGroupDao.deleteGroup(groupId, createdBy,
+ config.isSoftDeleteGroup());
+ group = userGroupDao.retrieveGroupById(groupId);
+ assertEquals(UserGroupStatus.DELETED, group.getStatus());
+ // hard delete
+ userGroupDao.deleteGroup(groupId, createdBy, false);
+ KustvaktException exception = assertThrows(KustvaktException.class,
+ () -> {
+ userGroupDao.retrieveGroupById(groupId);
+ });
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ exception.getStatusCode().intValue());
+ }
+
+ @Test
+ public void retrieveGroupWithMembers () throws KustvaktException {
+ // dory group
+ List<UserGroupMember> members = userGroupDao.retrieveGroupById(2, true)
+ .getMembers();
+ assertEquals(4, members.size());
+ UserGroupMember m = members.get(1);
+ Set<Role> roles = m.getRoles();
+ assertEquals(2, roles.size());
+ List<Role> sortedRoles = new ArrayList<>(roles);
+ Collections.sort(sortedRoles);
+ assertEquals(PredefinedRole.USER_GROUP_MEMBER.name(),
+ sortedRoles.get(0).getName());
+ assertEquals(PredefinedRole.VC_ACCESS_MEMBER.name(),
+ sortedRoles.get(1).getName());
+ }
+
+ @Test
+ public void retrieveGroupByUserId () throws KustvaktException {
+ List<UserGroup> group = userGroupDao.retrieveGroupByUserId("dory");
+ assertEquals(2, group.size());
+ group = userGroupDao.retrieveGroupByUserId("pearl");
+ assertEquals(0, group.size());
+ }
+
+ @Test
+ public void addVCToGroup () throws KustvaktException {
+ // dory group
+ int groupId = 2;
+ UserGroup group = userGroupDao.retrieveGroupById(groupId);
+ String createdBy = "dory";
+ String name = "dory new vc";
+ int id = virtualCorpusDao.createQuery(name, ResourceType.PROJECT,
+ QueryType.VIRTUAL_CORPUS, CorpusAccess.PUB, "corpusSigle=WPD15",
+ "", "", "", false, createdBy, null, null);
+ QueryDO virtualCorpus = virtualCorpusDao.retrieveQueryById(id);
+ userGroupDao.addQueryToGroup(virtualCorpus, createdBy,
+ QueryAccessStatus.ACTIVE, group);
+ List<QueryDO> vc = virtualCorpusDao.retrieveQueryByGroup(groupId);
+ assertEquals(2, vc.size());
+ assertEquals(name, vc.get(1).getName());
+ // delete vc from group
+ userGroupDao.deleteQueryFromGroup(virtualCorpus.getId(), groupId);
+ vc = virtualCorpusDao.retrieveQueryByGroup(groupId);
+ assertEquals(1, vc.size());
+ // delete vc
+ virtualCorpusDao.deleteQuery(virtualCorpus);
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/dao/UserGroupMemberDaoTest.java b/src/test/java/de/ids_mannheim/korap/dao/UserGroupMemberDaoTest.java
new file mode 100644
index 0000000..e3fdc87
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/dao/UserGroupMemberDaoTest.java
@@ -0,0 +1,52 @@
+package de.ids_mannheim.korap.dao;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import de.ids_mannheim.korap.constant.PredefinedRole;
+import de.ids_mannheim.korap.entity.Role;
+import de.ids_mannheim.korap.entity.UserGroupMember;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration("classpath:test-config.xml")
+public class UserGroupMemberDaoTest {
+
+ @Autowired
+ private UserGroupMemberDao dao;
+
+ @Autowired
+ private RoleDao roleDao;
+
+ @Test
+ public void testRetrieveMemberByRole () throws KustvaktException {
+ // dory group
+ List<UserGroupMember> vcaAdmins = dao.retrieveMemberByRole(2,
+ PredefinedRole.VC_ACCESS_ADMIN.getId());
+ // System.out.println(vcaAdmins);
+ assertEquals(1, vcaAdmins.size());
+ assertEquals(vcaAdmins.get(0).getUserId(), "dory");
+ }
+
+ @Test
+ public void testAddSameMemberRole () throws KustvaktException {
+ UserGroupMember member = dao.retrieveMemberById("dory", 1);
+ Set<Role> roles = member.getRoles();
+ Role adminRole = roleDao
+ .retrieveRoleById(PredefinedRole.USER_GROUP_ADMIN.getId());
+ roles.add(adminRole);
+ member.setRoles(roles);
+ dao.updateMember(member);
+ member = dao.retrieveMemberById("dory", 1);
+ member.getRoles();
+ assertEquals(2, roles.size());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/dao/VirtualCorpusAccessDaoTest.java b/src/test/java/de/ids_mannheim/korap/dao/VirtualCorpusAccessDaoTest.java
new file mode 100644
index 0000000..72f1e0f
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/dao/VirtualCorpusAccessDaoTest.java
@@ -0,0 +1,34 @@
+package de.ids_mannheim.korap.dao;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import de.ids_mannheim.korap.constant.QueryAccessStatus;
+import de.ids_mannheim.korap.entity.QueryAccess;
+import de.ids_mannheim.korap.entity.UserGroup;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration("classpath:test-config.xml")
+public class VirtualCorpusAccessDaoTest {
+
+ @Autowired
+ private QueryAccessDao dao;
+
+ @Test
+ public void getAccessByVC () throws KustvaktException {
+ List<QueryAccess> vcaList = dao.retrieveActiveAccessByQuery(2);
+ QueryAccess access = vcaList.get(0);
+ assertEquals(QueryAccessStatus.ACTIVE, access.getStatus());
+ assertEquals(access.getCreatedBy(), "dory");
+ UserGroup group = access.getUserGroup();
+ assertEquals(2, group.getId());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/dao/VirtualCorpusDaoTest.java b/src/test/java/de/ids_mannheim/korap/dao/VirtualCorpusDaoTest.java
new file mode 100644
index 0000000..cfd226b
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/dao/VirtualCorpusDaoTest.java
@@ -0,0 +1,152 @@
+package de.ids_mannheim.korap.dao;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Iterator;
+import java.util.List;
+
+import jakarta.persistence.PersistenceException;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.constant.QueryType;
+import de.ids_mannheim.korap.constant.ResourceType;
+import de.ids_mannheim.korap.entity.QueryDO;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.user.User;
+
+public class VirtualCorpusDaoTest extends SpringJerseyTest {
+
+ @Autowired
+ private QueryDao dao;
+
+ @Test
+ public void testListVCByType () throws KustvaktException {
+ List<QueryDO> vcList = dao.retrieveQueryByType(ResourceType.PUBLISHED,
+ null, QueryType.VIRTUAL_CORPUS);
+ assertEquals(1, vcList.size());
+ QueryDO vc = vcList.get(0);
+ assertEquals(4, vc.getId());
+ assertEquals(vc.getName(), "published-vc");
+ assertEquals(vc.getCreatedBy(), "marlin");
+ }
+
+ @Test
+ public void testSystemVC () throws KustvaktException {
+ // insert vc
+ int id = dao.createQuery("system-vc", ResourceType.SYSTEM,
+ QueryType.VIRTUAL_CORPUS, User.CorpusAccess.FREE,
+ "corpusSigle=GOE", "definition", "description", "experimental",
+ false, "test class", null, null);
+ // select vc
+ List<QueryDO> vcList = dao.retrieveQueryByType(ResourceType.SYSTEM,
+ null, QueryType.VIRTUAL_CORPUS);
+ assertEquals(2, vcList.size());
+ QueryDO vc = dao.retrieveQueryById(id);
+ // delete vc
+ dao.deleteQuery(vc);
+ // check if vc has been deleted
+ KustvaktException exception = assertThrows(KustvaktException.class,
+ () -> {
+ dao.retrieveQueryById(id);
+ });
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ exception.getStatusCode().intValue());
+ }
+
+ @Test
+ public void testNonUniqueVC () throws KustvaktException {
+
+ PersistenceException exception = assertThrows(
+ PersistenceException.class, () -> {
+ dao.createQuery("system-vc", ResourceType.SYSTEM,
+ QueryType.VIRTUAL_CORPUS, User.CorpusAccess.FREE,
+ "corpusSigle=GOE", "definition", "description",
+ "experimental", false, "system", null, null);
+ });
+
+ assertEquals(exception.getMessage(),
+ "Converting `org.hibernate.exception.GenericJDBCException` "
+ + "to JPA `PersistenceException` : could not execute statement");
+ }
+
+ @Test
+ public void retrieveSystemVC () throws KustvaktException {
+ List<QueryDO> vc = dao.retrieveQueryByType(ResourceType.SYSTEM, null,
+ QueryType.VIRTUAL_CORPUS);
+ assertEquals(1, vc.size());
+ }
+
+ /**
+ * retrieve private and group VC
+ *
+ * @throws KustvaktException
+ */
+ @Test
+ public void retrieveVCByUserDory () throws KustvaktException {
+ List<QueryDO> virtualCorpora = dao.retrieveQueryByUser("dory",
+ QueryType.VIRTUAL_CORPUS);
+ // System.out.println(virtualCorpora);
+ assertEquals(4, virtualCorpora.size());
+ // ordered by id
+ Iterator<QueryDO> i = virtualCorpora.iterator();
+ assertEquals(i.next().getName(), "dory-vc");
+ assertEquals(i.next().getName(), "group-vc");
+ assertEquals(i.next().getName(), "system-vc");
+ assertEquals(i.next().getName(), "published-vc");
+ }
+
+ /**
+ * retrieves group VC and
+ * excludes hidden published VC (user has never used it)
+ *
+ * @throws KustvaktException
+ */
+ @Test
+ public void retrieveVCByUserNemo () throws KustvaktException {
+ List<QueryDO> virtualCorpora = dao.retrieveQueryByUser("nemo",
+ QueryType.VIRTUAL_CORPUS);
+ assertEquals(3, virtualCorpora.size());
+ Iterator<QueryDO> i = virtualCorpora.iterator();
+ assertEquals(i.next().getName(), "group-vc");
+ assertEquals(i.next().getName(), "system-vc");
+ assertEquals(i.next().getName(), "nemo-vc");
+ }
+
+ /**
+ * retrieves published VC by the owner and
+ * excludes group vc when a user is a pending member
+ *
+ * @throws KustvaktException
+ */
+ @Test
+ public void retrieveVCByUserMarlin () throws KustvaktException {
+ List<QueryDO> virtualCorpora = dao.retrieveQueryByUser("marlin",
+ QueryType.VIRTUAL_CORPUS);
+ assertEquals(3, virtualCorpora.size());
+ Iterator<QueryDO> i = virtualCorpora.iterator();
+ assertEquals(i.next().getName(), "system-vc");
+ assertEquals(i.next().getName(), "published-vc");
+ assertEquals(i.next().getName(), "marlin-vc");
+ }
+
+ /**
+ * retrieves published VC from an auto-generated hidden group and
+ * excludes group vc when a user is a deleted member
+ *
+ * @throws KustvaktException
+ */
+ @Test
+ public void retrieveVCByUserPearl () throws KustvaktException {
+ List<QueryDO> virtualCorpora = dao.retrieveQueryByUser("pearl",
+ QueryType.VIRTUAL_CORPUS);
+ assertEquals(2, virtualCorpora.size());
+ Iterator<QueryDO> i = virtualCorpora.iterator();
+ assertEquals(i.next().getName(), "system-vc");
+ assertEquals(i.next().getName(), "published-vc");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/BCryptTest.java b/src/test/java/de/ids_mannheim/korap/misc/BCryptTest.java
new file mode 100644
index 0000000..ce1abb3
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/BCryptTest.java
@@ -0,0 +1,19 @@
+package de.ids_mannheim.korap.misc;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.mindrot.jbcrypt.BCrypt;
+
+public class BCryptTest {
+
+ @Test
+ public void testSalt () {
+ String salt = BCrypt.gensalt(8);
+ // System.out.println(salt);
+ String plain = "secret";
+ String password = BCrypt.hashpw(plain, salt);
+ // System.out.println(password);
+ assertTrue(BCrypt.checkpw(plain, password));
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/CollectionQueryBuilderTest.java b/src/test/java/de/ids_mannheim/korap/misc/CollectionQueryBuilderTest.java
new file mode 100644
index 0000000..0935f6c
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/CollectionQueryBuilderTest.java
@@ -0,0 +1,328 @@
+package de.ids_mannheim.korap.misc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.KoralCollectionQueryBuilder;
+
+/**
+ * @author hanl
+ * @date 12/08/2015
+ */
+public class CollectionQueryBuilderTest {
+
+ @Test
+ public void testsimpleAdd () throws KustvaktException {
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.with("corpusSigle=WPD");
+ JsonNode node = JsonUtils.readTree(b.toJSON());
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:doc");
+ assertEquals(node.at("/collection/key").asText(), "corpusSigle");
+ }
+
+ @Test
+ public void testSimpleConjunction () throws KustvaktException {
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.with("corpusSigle=WPD & textClass=freizeit");
+ JsonNode node = JsonUtils.readTree(b.toJSON());
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(node.at("/collection/operands/0/key").asText(),
+ "corpusSigle");
+ assertEquals(node.at("/collection/operands/1/key").asText(),
+ "textClass");
+ }
+
+ @Test
+ public void testSimpleDisjunction () throws KustvaktException {
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.with("corpusSigle=WPD | textClass=freizeit");
+ JsonNode node = JsonUtils.readTree(b.toJSON());
+ assertNotNull(node);
+ assert node.at("/collection/operation").asText().equals("operation:or");
+ assert node.at("/collection/operands/0/key").asText()
+ .equals("corpusSigle");
+ assert node.at("/collection/operands/1/key").asText()
+ .equals("textClass");
+ }
+
+ @Test
+ public void testComplexSubQuery () throws KustvaktException {
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.with("(corpusSigle=WPD) | (textClass=freizeit & corpusSigle=BRZ13)");
+ JsonNode node = JsonUtils.readTree(b.toJSON());
+ assertNotNull(node);
+ assert node.at("/collection/operation").asText().equals("operation:or");
+ assert node.at("/collection/operands/0/key").asText()
+ .equals("corpusSigle");
+ assert node.at("/collection/operands/1/@type").asText()
+ .equals("koral:docGroup");
+ }
+
+ @Test
+ public void testAddResourceQueryAfter () throws KustvaktException {
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.with("(textClass=politik & title=\"random title\") | textClass=wissenschaft");
+ KoralCollectionQueryBuilder c = new KoralCollectionQueryBuilder();
+ c.setBaseQuery(b.toJSON());
+ c.with("corpusSigle=WPD");
+ JsonNode node = JsonUtils.readTree(c.toJSON());
+ assertNotNull(node);
+ assertEquals(node.at("/collection/operands/1/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/0/@type").asText(),
+ "koral:docGroup");
+ assertEquals(2, node.at("/collection/operands").size());
+ assertEquals(2, node.at("/collection/operands/0/operands").size());
+ assertEquals(2,
+ node.at("/collection/operands/0/operands/0/operands").size());
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(node.at("/collection/operands/0/operation").asText(),
+ "operation:or");
+ assertEquals(
+ node.at("/collection/operands/0/operands/0/operation").asText(),
+ "operation:and");
+ assertEquals(node.at("/collection/operands/1/value").asText(), "WPD");
+ }
+
+ @Test
+ public void testAddComplexResourceQueryAfter () throws KustvaktException {
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.with("(title=\"random title\") | (textClass=wissenschaft)");
+ KoralCollectionQueryBuilder c = new KoralCollectionQueryBuilder();
+ c.setBaseQuery(b.toJSON());
+ c.with("(corpusSigle=BRZ13 | corpusSigle=AZPS)");
+ JsonNode node = JsonUtils.readTree(c.toJSON());
+ assertNotNull(node);
+ assertEquals(node.at("/collection/operands/0/@type").asText(),
+ "koral:docGroup");
+ assertEquals(node.at("/collection/operands/1/@type").asText(),
+ "koral:docGroup");
+ assertEquals(
+ node.at("/collection/operands/1/operands/0/value").asText(),
+ "BRZ13");
+ assertEquals(
+ node.at("/collection/operands/1/operands/1/value").asText(),
+ "AZPS");
+ assertEquals(
+ node.at("/collection/operands/0/operands/0/value").asText(),
+ "random title");
+ assertEquals(
+ node.at("/collection/operands/0/operands/1/value").asText(),
+ "wissenschaft");
+ }
+
+ @Test
+ public void testBuildQuery () throws KustvaktException {
+ String coll = "corpusSigle=WPD";
+ String query = "[base=Haus]";
+ QuerySerializer check = new QuerySerializer();
+ check.setQuery(query, "poliqarp");
+ check.setCollection(coll);
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.setBaseQuery(check.toJSON());
+ b.with("textClass=freizeit");
+ JsonNode res = (JsonNode) b.rebaseCollection();
+ assertNotNull(res);
+ assertEquals(res.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(res.at("/collection/operation").asText(), "operation:and");
+ assertEquals(res.at("/collection/operands/0/@type").asText(),
+ "koral:doc");
+ assertEquals(res.at("/collection/operands/1/value").asText(),
+ "freizeit");
+ assertEquals(res.at("/collection/operands/1/key").asText(),
+ "textClass");
+ assertEquals(res.at("/collection/operands/1/@type").asText(),
+ "koral:doc");
+ assertEquals(res.at("/collection/operands/0/value").asText(), "WPD");
+ assertEquals(res.at("/collection/operands/0/key").asText(),
+ "corpusSigle");
+ // check also that query is still there
+ assertEquals(res.at("/query/@type").asText(), "koral:token");
+ assertEquals(res.at("/query/wrap/@type").asText(), "koral:term");
+ assertEquals(res.at("/query/wrap/key").asText(), "Haus");
+ assertEquals(res.at("/query/wrap/layer").asText(), "lemma");
+ }
+
+ @Test
+ public void testBaseQueryBuild () throws KustvaktException {
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.with("(corpusSigle=ADF) | (textClass=freizeit & corpusSigle=WPD)");
+ KoralCollectionQueryBuilder c = new KoralCollectionQueryBuilder();
+ c.setBaseQuery(b.toJSON());
+ c.with("corpusSigle=BRZ13");
+ JsonNode base = (JsonNode) c.rebaseCollection();
+ assertNotNull(base);
+ assertEquals(base.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(base.at("/collection/operands/1/@type").asText(),
+ "koral:doc");
+ assertEquals(base.at("/collection/operands/1/value").asText(), "BRZ13");
+ assertEquals(base.at("/collection/operands/0/@type").asText(),
+ "koral:docGroup");
+ assertEquals(base.at("/collection/operands/0/operands").size(), 2);
+ }
+
+ @Test
+ public void testNodeMergeWithBase () throws KustvaktException {
+ String coll = "corpusSigle=WPD";
+ String query = "[base=Haus]";
+ QuerySerializer check = new QuerySerializer();
+ check.setQuery(query, "poliqarp");
+ check.setCollection(coll);
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.setBaseQuery(check.toJSON());
+ KoralCollectionQueryBuilder test = new KoralCollectionQueryBuilder();
+ test.with("textClass=wissenschaft | textClass=politik");
+ JsonNode node = (JsonNode) test.rebaseCollection();
+ node = b.mergeWith(node);
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(2, node.at("/collection/operands").size());
+ }
+
+ @Test
+ public void testNodeMergeWithoutBase () throws KustvaktException {
+ String query = "[base=Haus]";
+ QuerySerializer check = new QuerySerializer();
+ check.setQuery(query, "poliqarp");
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.setBaseQuery(check.toJSON());
+ KoralCollectionQueryBuilder test = new KoralCollectionQueryBuilder();
+ test.with("corpusSigle=WPD");
+ // String json = test.toJSON();
+ // System.out.println(json);
+ // JsonNode node = (JsonNode) test.rebaseCollection(null);
+ // node = b.mergeWith(node);
+ // assertNotNull(node);
+ // assertEquals("koral:doc", node.at("/collection/@type").asText());
+ // assertEquals("corpusSigle", node.at("/collection/key").asText());
+ }
+
+ @Test
+ public void testNodeMergeWithoutBaseWrongOperator ()
+ throws KustvaktException {
+ String query = "[base=Haus]";
+ QuerySerializer check = new QuerySerializer();
+ check.setQuery(query, "poliqarp");
+ KoralCollectionQueryBuilder b = new KoralCollectionQueryBuilder();
+ b.setBaseQuery(check.toJSON());
+ KoralCollectionQueryBuilder test = new KoralCollectionQueryBuilder();
+ // operator is not supposed to be here!
+ test.and().with("corpusSigle=WPD");
+ // String json = test.toJSON();
+ // System.out.println(json);
+ // JsonNode node = (JsonNode) test.rebaseCollection(null);
+ // node = b.mergeWith(node);
+ // assertNotNull(node);
+ // assertEquals("koral:doc", node.at("/collection/@type").asText());
+ // assertEquals("corpusSigle", node.at("/collection/key").asText());
+ }
+
+ @Test
+ public void testStoredCollectionBaseQueryBuild () {}
+
+ @Test
+ public void testAddOROperator () throws KustvaktException {
+ String coll = "corpusSigle=WPD";
+ String query = "[base=Haus]";
+ QuerySerializer check = new QuerySerializer();
+ check.setQuery(query, "poliqarp");
+ check.setCollection(coll);
+ KoralCollectionQueryBuilder test = new KoralCollectionQueryBuilder();
+ test.setBaseQuery(check.toJSON());
+ test.or().with("textClass=wissenschaft | textClass=politik");
+ JsonNode node = (JsonNode) test.rebaseCollection();
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operation").asText(), "operation:or");
+ assertEquals(2, node.at("/collection/operands/1/operands").size());
+ }
+
+ @Test
+ public void testAddANDOperator () throws KustvaktException {
+ String coll = "corpusSigle=WPD";
+ String query = "[base=Haus]";
+ QuerySerializer check = new QuerySerializer();
+ check.setQuery(query, "poliqarp");
+ check.setCollection(coll);
+ KoralCollectionQueryBuilder test = new KoralCollectionQueryBuilder();
+ test.setBaseQuery(check.toJSON());
+ test.and().with("textClass=wissenschaft | textClass=politik");
+ JsonNode node = (JsonNode) test.rebaseCollection();
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(2, node.at("/collection/operands/1/operands").size());
+ }
+
+ @Test
+ public void testAddDefaultOperator () throws KustvaktException {
+ String coll = "corpusSigle=WPD";
+ String query = "[base=Haus]";
+ QuerySerializer check = new QuerySerializer();
+ check.setQuery(query, "poliqarp");
+ check.setCollection(coll);
+ KoralCollectionQueryBuilder test = new KoralCollectionQueryBuilder();
+ test.setBaseQuery(check.toJSON());
+ test.with("textClass=wissenschaft | textClass=politik");
+ JsonNode node = (JsonNode) test.rebaseCollection();
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(2, node.at("/collection/operands/1/operands").size());
+ }
+
+ @Test
+ public void testBaseCollectionNull () throws KustvaktException {
+ // base is missing collection segment
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[base=Haus]", "poliqarp");
+ KoralCollectionQueryBuilder total = new KoralCollectionQueryBuilder();
+ total.setBaseQuery(s.toJSON());
+ KoralCollectionQueryBuilder builder = new KoralCollectionQueryBuilder();
+ builder.with("textClass=politik & corpusSigle=WPD");
+ JsonNode node = total.and()
+ .mergeWith((JsonNode) builder.rebaseCollection());
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(node.at("/collection/operands/0/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/1/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/0/key").asText(),
+ "textClass");
+ assertEquals(node.at("/collection/operands/1/key").asText(),
+ "corpusSigle");
+ }
+
+ @Test
+ public void testMergeCollectionNull () throws KustvaktException {
+ // merge json is missing collection segment
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[base=Haus]", "poliqarp");
+ s.setCollection("textClass=wissenschaft");
+ KoralCollectionQueryBuilder total = new KoralCollectionQueryBuilder();
+ total.setBaseQuery(s.toJSON());
+ KoralCollectionQueryBuilder builder = new KoralCollectionQueryBuilder();
+ JsonNode node = total.and()
+ .mergeWith((JsonNode) builder.rebaseCollection());
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:doc");
+ assertEquals(node.at("/collection/key").asText(), "textClass");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/ConfigTest.java b/src/test/java/de/ids_mannheim/korap/misc/ConfigTest.java
new file mode 100644
index 0000000..f683230
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/ConfigTest.java
@@ -0,0 +1,55 @@
+package de.ids_mannheim.korap.misc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import de.ids_mannheim.korap.config.ConfigLoader;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.utils.ServiceInfo;
+import de.ids_mannheim.korap.utils.TimeUtils;
+
+/**
+ * @author hanl
+ * @date 02/09/2015
+ */
+public class ConfigTest extends SpringJerseyTest {
+
+ @Autowired
+ KustvaktConfiguration config;
+
+ @Test
+ public void testConfigLoader () {
+ InputStream stream = ConfigLoader.loadConfigStream("kustvakt.conf");
+ assertNotNull(stream);
+ }
+
+ @Test
+ public void testPropertyLoader () throws IOException {
+ Properties p = ConfigLoader.loadProperties("kustvakt.conf");
+ assertNotNull(p);
+ }
+
+ @Test
+ public void testServiceInfo () {
+ String version = ServiceInfo.getInfo().getVersion();
+ String name = ServiceInfo.getInfo().getName();
+ assertNotEquals("UNKNOWN", version, "wrong version");
+ assertNotEquals("UNKNOWN", name, "wrong name");
+ }
+
+ @Test
+ public void testProperties () {
+ assertEquals("opennlp", config.getDefault_orthography(),
+ "token layer does not match");
+ assertEquals(TimeUtils.convertTimeToSeconds("1D"),
+ config.getLongTokenTTL(), "token expiration does not match");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/KoralNodeTest.java b/src/test/java/de/ids_mannheim/korap/misc/KoralNodeTest.java
new file mode 100644
index 0000000..eb9051b
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/KoralNodeTest.java
@@ -0,0 +1,48 @@
+package de.ids_mannheim.korap.misc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import de.ids_mannheim.korap.rewrite.KoralNode;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author hanl
+ * @date 21/10/2015
+ */
+// todo: complete tests
+public class KoralNodeTest {
+
+ // todo: 21.10.15 --> e.g. injection does not tell you if an entire node was injected, or just a value!
+ @Test
+ public void addToNode () {
+ ObjectNode node = JsonUtils.createObjectNode();
+ KoralNode knode = KoralNode.wrapNode(node);
+ knode.put("value_1", "setting_1");
+ assertEquals(knode.rawNode().toString(), "{\"value_1\":\"setting_1\"}");
+ }
+
+ @Test
+ public void removeFromNode () {
+ ObjectNode node = JsonUtils.createObjectNode();
+ node.put("value_1", "setting_1");
+ KoralNode knode = KoralNode.wrapNode(node);
+ knode.remove("value_1", null);
+ assertEquals(knode.rawNode().toString(), "{}");
+ }
+
+ @Test
+ public void replaceObject () {
+ ObjectNode node = JsonUtils.createObjectNode();
+ node.put("value_1", "setting_1");
+ KoralNode knode = KoralNode.wrapNode(node);
+ knode.replace("value_1", "settings_2", null);
+ assertEquals(knode.rawNode().toString(),
+ "{\"value_1\":\"settings_2\"}");
+ }
+
+ // todo: 21.10.15 --> if a node is injected, that node must contain a "rewrites" reference?!
+ @Test
+ public void addNodeToKoral () {}
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/LocalQueryTest.java b/src/test/java/de/ids_mannheim/korap/misc/LocalQueryTest.java
new file mode 100644
index 0000000..9591a0e
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/LocalQueryTest.java
@@ -0,0 +1,71 @@
+package de.ids_mannheim.korap.misc;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+
+import jakarta.annotation.PostConstruct;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import de.ids_mannheim.korap.KrillCollection;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.query.serialize.CollectionQueryProcessor;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.KoralCollectionQueryBuilder;
+import de.ids_mannheim.korap.web.SearchKrill;
+
+/**
+ * @author hanl
+ * @date 14/01/2016
+ */
+public class LocalQueryTest extends SpringJerseyTest {
+
+ private static String index;
+
+ @Autowired
+ KustvaktConfiguration config;
+
+ @PostConstruct
+ public void setup () throws Exception {
+ index = config.getIndexDir();
+ }
+
+ @Test
+ public void testQuery () throws KustvaktException {
+ String qstring = "creationDate since 1786 & creationDate until 1788";
+ // qstring = "creationDate since 1765 & creationDate until 1768";
+ // qstring = "textType = Aphorismus";
+ // qstring = "title ~ \"Werther\"";
+ SearchKrill krill = new SearchKrill(index);
+ KoralCollectionQueryBuilder coll = new KoralCollectionQueryBuilder();
+ coll.with(qstring);
+ String stats = krill.getStatistics(coll.toJSON());
+ assert stats != null && !stats.isEmpty() && !stats.equals("null");
+ }
+
+ @Test
+ public void testCollQuery () throws IOException, KustvaktException {
+ String qstring = "creationDate since 1800 & creationDate until 1820";
+ CollectionQueryProcessor processor = new CollectionQueryProcessor();
+ processor.process(qstring);
+ String s = JsonUtils.toJSON(processor.getRequestMap());
+ KrillCollection c = new KrillCollection(s);
+ c.setIndex(new SearchKrill(index).getIndex());
+ long docs = c.numberOf("documents");
+ assert docs > 0 && docs < 15;
+ }
+
+ @Test
+ public void testCollQuery2 () throws IOException {
+ String query = "{\"@context\":\"http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld\",\"errors\":[],\"warnings\":[],\"messages\":[],\"collection\":{\"@type\":\"koral:docGroup\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:doc\",\"key\":\"creationDate\",\"type\":\"type:date\",\"value\":\"1786\",\"match\":\"match:geq\"},{\"@type\":\"koral:doc\",\"key\":\"creationDate\",\"type\":\"type:date\",\"value\":\"1788\",\"match\":\"match:leq\"}]},\"query\":{},\"meta\":{}}";
+ KrillCollection c = new KrillCollection(query);
+ c.setIndex(new SearchKrill(index).getIndex());
+ long sent = c.numberOf("base/sentences");
+ long docs = c.numberOf("documents");
+ assertNotNull(sent);
+ assertNotNull(docs);
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/MapUtilsTest.java b/src/test/java/de/ids_mannheim/korap/misc/MapUtilsTest.java
new file mode 100644
index 0000000..4d59790
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/MapUtilsTest.java
@@ -0,0 +1,38 @@
+package de.ids_mannheim.korap.misc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import de.ids_mannheim.korap.web.utils.MapUtils;
+import edu.emory.mathcs.backport.java.util.Arrays;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import jakarta.ws.rs.core.MultivaluedMap;
+
+public class MapUtilsTest {
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testConvertToMap () {
+ MultivaluedMap<String, String> mm = new MultivaluedHashMap<String, String>();
+ mm.put("k1", Arrays.asList(new String[] { "a", "b", "c" }));
+ mm.put("k2", Arrays.asList(new String[] { "d", "e", "f" }));
+ Map<String, String> map = MapUtils.toMap(mm);
+ assertEquals(map.get("k1"), "a b c");
+ assertEquals(map.get("k2"), "d e f");
+ }
+
+ @Test
+ public void testConvertNullMap () {
+ Map<String, String> map = MapUtils.toMap(null);
+ assertEquals(0, map.size());
+ }
+
+ @Test
+ public void testConvertEmptyMap () {
+ MultivaluedMap<String, String> mm = new MultivaluedHashMap<String, String>();
+ Map<String, String> map = MapUtils.toMap(mm);
+ assertEquals(0, map.size());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/MetaQueryBuilderTest.java b/src/test/java/de/ids_mannheim/korap/misc/MetaQueryBuilderTest.java
new file mode 100644
index 0000000..7eda84d
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/MetaQueryBuilderTest.java
@@ -0,0 +1,26 @@
+package de.ids_mannheim.korap.misc;
+
+import de.ids_mannheim.korap.config.QueryBuilderUtil;
+import de.ids_mannheim.korap.query.serialize.MetaQueryBuilder;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Created by hanl on 17.04.16.
+ */
+public class MetaQueryBuilderTest {
+
+ @Test
+ public void testSpanContext () {
+ MetaQueryBuilder m = QueryBuilderUtil.defaultMetaBuilder(0, 1, 5,
+ "sentence", false);
+ Map<?, ?> map = m.raw();
+ assertEquals(map.get("context"), "sentence");
+ assertEquals(1, map.get("startPage"));
+ assertEquals(0, map.get("startIndex"));
+ assertEquals(false, map.get("cutOff"));
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/RegexTest.java b/src/test/java/de/ids_mannheim/korap/misc/RegexTest.java
new file mode 100644
index 0000000..634530b
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/RegexTest.java
@@ -0,0 +1,20 @@
+package de.ids_mannheim.korap.misc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.regex.Matcher;
+
+import org.junit.jupiter.api.Test;
+import de.ids_mannheim.korap.annotation.AnnotationParser;
+
+public class RegexTest {
+
+ @Test
+ public void testQuote () {
+ String s = "ah[\"-\"]";
+ Matcher m = AnnotationParser.quotePattern.matcher(s);
+ if (m.find()) {
+ assertEquals(m.group(1), "-");
+ }
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/ScopesTest.java b/src/test/java/de/ids_mannheim/korap/misc/ScopesTest.java
new file mode 100644
index 0000000..ad92c36
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/ScopesTest.java
@@ -0,0 +1,16 @@
+package de.ids_mannheim.korap.misc;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author hanl
+ * @date 20/01/2016
+ */
+public class ScopesTest {
+
+ @Test
+ public void testScopes () {}
+
+ @Test
+ public void testOpenIDScopes () {}
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/SerializationTest.java b/src/test/java/de/ids_mannheim/korap/misc/SerializationTest.java
new file mode 100644
index 0000000..dd4ca5b
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/SerializationTest.java
@@ -0,0 +1,23 @@
+package de.ids_mannheim.korap.misc;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author hanl
+ * @date 21/01/2016
+ */
+@Disabled
+public class SerializationTest {
+
+ @Test
+ public void testSettingsObject () {
+ // String t = "poliqarp_test";
+ // UserSettings s = new UserSettings();
+ // Map map = s.toObjectMap();
+ // map.put(Attributes.QUERY_LANGUAGE, t);
+ // s.updateObjectSettings(map);
+ //
+ // assert s.getQueryLanguage().equals(t);
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/ServiceSuite.java b/src/test/java/de/ids_mannheim/korap/misc/ServiceSuite.java
new file mode 100644
index 0000000..ac2c8a1
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/ServiceSuite.java
@@ -0,0 +1,7 @@
+package de.ids_mannheim.korap.misc;
+
+/**
+ * @author hanl
+ * @date 29/02/2016
+ */
+public class ServiceSuite {}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/StringUtilsTest.java b/src/test/java/de/ids_mannheim/korap/misc/StringUtilsTest.java
new file mode 100644
index 0000000..f7dfb3a
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/StringUtilsTest.java
@@ -0,0 +1,38 @@
+package de.ids_mannheim.korap.misc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.codec.binary.Base64;
+import org.junit.jupiter.api.Test;
+import de.ids_mannheim.korap.authentication.http.AuthorizationData;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.authentication.http.TransferEncoding;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.StringUtils;
+
+/**
+ * Created by hanl on 29.05.16.
+ */
+public class StringUtilsTest {
+
+ @Test
+ public void testTextIToDoc () {
+ String textSigle = "WPD_AAA.02439";
+ String docSigle = "WPD_AAA";
+ assertEquals(docSigle, StringUtils.getDocSigle(textSigle));
+ assertEquals(docSigle, StringUtils.getDocSigle(docSigle));
+ }
+
+ @Test
+ public void testBasicHttpSplit () throws KustvaktException {
+ String s2 = new String(Base64.encodeBase64("test:testPass".getBytes()));
+ String[] f2 = TransferEncoding.decodeBase64(s2);
+ assertEquals(f2[0], "test");
+ assertEquals(f2[1], "testPass");
+ HttpAuthorizationHandler handler = new HttpAuthorizationHandler();
+ String s1 = "basic "
+ + new String(Base64.encodeBase64("test:testPass".getBytes()));
+ AuthorizationData f1 = handler.parseAuthorizationHeaderValue(s1);
+ assertEquals(s2, f1.getToken());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/misc/TestNullableSqls.java b/src/test/java/de/ids_mannheim/korap/misc/TestNullableSqls.java
new file mode 100644
index 0000000..8fcb1d0
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/misc/TestNullableSqls.java
@@ -0,0 +1,7 @@
+package de.ids_mannheim.korap.misc;
+
+/**
+ * @author hanl
+ * @date 30/01/2016
+ */
+public class TestNullableSqls {}
diff --git a/src/test/java/de/ids_mannheim/korap/rewrite/CollectionRewriteTest.java b/src/test/java/de/ids_mannheim/korap/rewrite/CollectionRewriteTest.java
new file mode 100644
index 0000000..e205ce3
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/rewrite/CollectionRewriteTest.java
@@ -0,0 +1,277 @@
+package de.ids_mannheim.korap.rewrite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.config.TestVariables;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author hanl
+ * @date 03/09/2015
+ */
+public class CollectionRewriteTest extends SpringJerseyTest {
+
+ @Autowired
+ public KustvaktConfiguration config;
+
+ @Test
+ public void testCollectionNodeRemoveCorpusIdNoErrors ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionConstraint.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ s.setCollection("textClass=politik & corpusSigle=WPD");
+ String result = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(result,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(1, node.at("/collection/operands").size());
+ }
+
+ @Test
+ public void testCollectionNodeDeletionNotification () {}
+
+ @Test
+ public void testCollectionNodeRemoveAllCorpusIdNoErrors ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionConstraint.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ s.setCollection("corpusSigle=BRZ13 & corpusSigle=WPD");
+ String result = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(result,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(0, node.at("/collection/operands").size());
+ assertEquals(node.at("/collection/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ }
+
+ @Test
+ public void testCollectionNodeRemoveGroupedCorpusIdNoErrors ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionConstraint.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ s.setCollection(
+ "(corpusSigle=BRZ13 & textClass=Wissenschaft) & corpusSigle=WPD");
+ String result = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(result,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(node.at("/collection/operands/0/@type").asText(),
+ "koral:docGroup");
+ assertEquals(node.at("/collection/operands/0/operands/0/key").asText(),
+ "textClass");
+ assertEquals(node.at("/collection/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ }
+
+ // fixme: will probably fail when one doc groups are being refactored
+ @Test
+ public void testCollectionCleanEmptyDocGroupNoErrors ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionConstraint.class);
+ handler.add(CollectionCleanRewrite.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ s.setCollection(
+ "(corpusSigle=BRZ13 & corpusSigle=WPD) & textClass=Wissenschaft & textClass=Sport");
+ String result = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(result,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(2, node.at("/collection/operands").size());
+ assertEquals(node.at("/collection/operands/0/key").asText(),
+ "textClass");
+ assertEquals(node.at("/collection/operands/1/key").asText(),
+ "textClass");
+ assertEquals(node.at("/collection/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ }
+
+ @Test
+ public void testCollectionCleanMoveOneDocFromGroupUpNoErrors ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionConstraint.class);
+ handler.add(CollectionCleanRewrite.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ s.setCollection("(corpusSigle=BRZ13 & textClass=wissenschaft)");
+ String result = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(result,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:doc");
+ assertEquals(node.at("/collection/key").asText(), "textClass");
+ assertEquals(node.at("/collection/value").asText(), "wissenschaft");
+ assertEquals(node.at("/collection/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ }
+
+ @Test
+ public void testCollectionCleanEmptyGroupAndMoveOneFromGroupUpNoErrors ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionConstraint.class);
+ handler.add(CollectionCleanRewrite.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ s.setCollection(
+ "(corpusSigle=BRZ13 & corpusSigle=WPD) & textClass=Wissenschaft");
+ String result = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(result,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:doc");
+ assertEquals(node.at("/collection/key").asText(), "textClass");
+ assertEquals(node.at("/collection/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ }
+
+ @Test
+ public void testCollectionRemoveAndMoveOneFromGroupUpNoErrors ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionConstraint.class);
+ handler.add(CollectionCleanRewrite.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ s.setCollection(
+ "(docID=random & textClass=Wissenschaft) & corpusSigle=WPD");
+ String org = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(org,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(2, node.at("/collection/operands").size());
+ assertEquals(node.at("/collection/operands/0/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/1/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ }
+
+ @Test
+ public void testPublicCollectionRewriteEmptyAdd ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionRewrite.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ String org = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(org,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(node.at("/collection/key").asText(), "availability");
+ assertEquals(node.at("/collection/value").asText(), "CC-BY.*");
+ assertEquals(node.at("/collection/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(FREE)");
+ // todo:
+ }
+
+ @Test
+ public void testPublicCollectionRewriteNonEmptyAdd ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionRewrite.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ s.setCollection("(docSigle=WPD_AAA & textClass=wissenschaft)");
+ String org = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(org,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(2, node.at("/collection/operands").size());
+ assertEquals(node.at("/collection/operands/0/key").asText(),
+ "availability");
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "CC-BY.*");
+ assertEquals(node.at("/collection/operands/1/operands/0/key").asText(),
+ "docSigle");
+ assertEquals(node.at("/collection/operands/1/operands/1/key").asText(),
+ "textClass");
+ assertEquals(node.at("/collection/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(FREE)");
+ }
+
+ @Test
+ public void testRemoveCorpusFromDifferentGroups ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionConstraint.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ s.setCollection(
+ "(corpusSigle=BRZ14 & textClass=wissenschaft) | (corpusSigle=AZPR | textClass=freizeit)");
+ String org = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(org,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(2, node.at("/collection/operands").size());
+ assertEquals(node.at("/collection/operands/0/@type").asText(),
+ "koral:docGroup");
+ assertEquals(node.at("/collection/operands/1/@type").asText(),
+ "koral:docGroup");
+ assertEquals(1, node.at("/collection/operands/0/operands").size());
+ assertEquals(1, node.at("/collection/operands/1/operands").size());
+ assertEquals(node.at("/collection/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ }
+
+ @Test
+ public void testRemoveOneCorpusAndMoveDocFromTwoGroups ()
+ throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ handler.add(CollectionConstraint.class);
+ // todo: use this collection query also to test clean up filter! after reduction of corpusSigle (wiss | freizeit)!
+ handler.add(CollectionCleanRewrite.class);
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(TestVariables.SIMPLE_ADD_QUERY, "poliqarp");
+ s.setCollection(
+ "(corpusSigle=BRZ14 & textClass=wissenschaft) | (corpusSigle=AZPR | textClass=freizeit)");
+ String org = s.toJSON();
+ JsonNode node = JsonUtils.readTree(handler.processQuery(org,
+ User.UserFactory.getUser("test_user")));
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(2, node.at("/collection/operands").size());
+ assertEquals(node.at("/collection/operands/0/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/0/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/0/key").asText(),
+ "textClass");
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "wissenschaft");
+ assertEquals(node.at("/collection/operands/1/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/1/key").asText(),
+ "textClass");
+ assertEquals(node.at("/collection/operands/1/value").asText(),
+ "freizeit");
+ assertEquals(node.at("/collection/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/rewrite/FoundryRewriteTest.java b/src/test/java/de/ids_mannheim/korap/rewrite/FoundryRewriteTest.java
new file mode 100644
index 0000000..51e7fc5
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/rewrite/FoundryRewriteTest.java
@@ -0,0 +1,189 @@
+package de.ids_mannheim.korap.rewrite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.user.KorAPUser;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+/**
+ * @author hanl, margaretha
+ * @date 18/06/2015
+ */
+// MH todo: check position and information of rewrites!
+public class FoundryRewriteTest extends SpringJerseyTest {
+
+ // private static String simple_add_query = "[pos=ADJA]";
+ // private static String simple_rewrite_query = "[base=Haus]";
+ // private static String complex_rewrite_query = "<c=INFC>";
+ // private static String complex_rewrite_query2 = "[orth=laufe/i & base!=Lauf]";
+ // private static String complex_rewrite_query3 = "[(base=laufen | base=gehen) & tt/pos=VVFIN]";
+ @Autowired
+ public KustvaktConfiguration config;
+
+ @Autowired
+ public RewriteHandler handler;
+
+ @Autowired
+ private LayerMapper m;
+
+ @Test
+ public void testSearchRewriteFoundryWithUserSetting ()
+ throws KustvaktException {
+ // create user setting
+ String json = "{\"pos-foundry\":\"opennlp\"}";
+ String username = "foundryRewriteTest";
+ String pathUsername = "~" + username;
+ Response response = target().path(API_VERSION).path(pathUsername)
+ .path("setting").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .put(Entity.json(json));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ // search
+ response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[pos=ADJA]").queryParam("ql", "poliqarp")
+ .request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .accept(MediaType.APPLICATION_JSON).get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/query/wrap/foundry").asText(), "opennlp");
+ assertEquals(node.at("/query/wrap/rewrites/0/scope").asText(),
+ "foundry");
+ }
+
+ @Test
+ public void testRewritePosFoundryWithUserSetting ()
+ throws KustvaktException {
+ // EM: see
+ // full/src/main/resources/db/insert/V3.6__insert_default_settings.sql
+ String username = "bubbles";
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[pos=ADJA]", "poliqarp");
+ String result = handler.processQuery(s.toJSON(),
+ new KorAPUser(username));
+ JsonNode node = JsonUtils.readTree(result);
+ assertEquals(node.at("/query/wrap/foundry").asText(), "corenlp");
+ assertEquals(node.at("/query/wrap/rewrites/0/scope").asText(),
+ "foundry");
+ }
+
+ @Test
+ public void testRewriteLemmaFoundryWithUserSetting ()
+ throws KustvaktException {
+ String username = "bubbles";
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[base=Haus]", "poliqarp");
+ String result = handler.processQuery(s.toJSON(),
+ new KorAPUser(username));
+ JsonNode node = JsonUtils.readTree(result);
+ // EM: only for testing, in fact, opennlp lemma does not
+ // exist!
+ assertEquals(node.at("/query/wrap/foundry").asText(), "opennlp");
+ assertEquals(node.at("/query/wrap/rewrites/0/scope").asText(),
+ "foundry");
+ }
+
+ @Test
+ public void testDefaultLayerMapperThrowsNoException () {
+ assertEquals(config.getDefault_lemma(), m.findFoundry("lemma"));
+ assertEquals(config.getDefault_pos(), m.findFoundry("pos"));
+ assertEquals(config.getDefault_orthography(), m.findFoundry("surface"));
+ assertEquals(config.getDefault_dep(), m.findFoundry("d"));
+ assertEquals(config.getDefault_const(), m.findFoundry("c"));
+ }
+
+ @Test
+ public void testDefaultFoundryInjectLemmaThrowsNoError ()
+ throws KustvaktException {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[base=Haus]", "poliqarp");
+ String result = handler.processQuery(s.toJSON(), new KorAPUser("test"));
+ JsonNode node = JsonUtils.readTree(result);
+ assertNotNull(node);
+ assertFalse(node.at("/query/wrap/foundry").isMissingNode());
+ assertEquals(config.getDefault_lemma(),
+ node.at("/query/wrap/foundry").asText());
+ assertEquals(node.at("/query/wrap/layer").asText(), "lemma");
+ assertFalse(node.at("/query/wrap/rewrites").isMissingNode());
+ assertEquals(node.at("/query/wrap/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ }
+
+ @Test
+ public void testDefaultFoundryInjectPOSNoErrors ()
+ throws KustvaktException {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[pos=ADJA]", "poliqarp");
+ String result = handler.processQuery(s.toJSON(), new KorAPUser("test"));
+ JsonNode node = JsonUtils.readTree(result);
+ assertNotNull(node);
+ assertFalse(node.at("/query/wrap/foundry").isMissingNode());
+ assertEquals(config.getDefault_pos(),
+ node.at("/query/wrap/foundry").asText());
+ assertEquals(node.at("/query/wrap/layer").asText(), "pos");
+ assertFalse(node.at("/query/wrap/rewrites").isMissingNode());
+ assertEquals(node.at("/query/wrap/rewrites/0/@type").asText(),
+ "koral:rewrite");
+ }
+
+ @Test
+ public void testFoundryInjectJoinedQueryNoErrors ()
+ throws KustvaktException {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[orth=laufe/i & base!=Lauf]", "poliqarp");
+ String result = handler.processQuery(s.toJSON(), new KorAPUser("test"));
+ JsonNode node = JsonUtils.readTree(result);
+ assertNotNull(node);
+ assertEquals(node.at("/query/wrap/@type").asText(), "koral:termGroup");
+ assertFalse(node.at("/query/wrap/operands/0/foundry").isMissingNode());
+ assertFalse(node.at("/query/wrap/operands/0/rewrites").isMissingNode());
+ assertFalse(node.at("/query/wrap/operands/1/foundry").isMissingNode());
+ assertFalse(node.at("/query/wrap/operands/1/rewrites").isMissingNode());
+ }
+
+ @Test
+ public void testFoundryInjectGroupedQueryNoErrors ()
+ throws KustvaktException {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[(base=laufen | tt/pos=VVFIN)]", "poliqarp");
+ String result = handler.processQuery(s.toJSON(), new KorAPUser("test"));
+ JsonNode node = JsonUtils.readTree(result);
+ assertNotNull(node);
+ assertEquals(node.at("/query/wrap/@type").asText(), "koral:termGroup");
+ assertFalse(node.at("/query/wrap/operands/0/foundry").isMissingNode());
+ assertFalse(node.at("/query/wrap/operands/0/rewrites").isMissingNode());
+ assertFalse(node.at("/query/wrap/operands/1/foundry").isMissingNode());
+ assertTrue(node.at("/query/wrap/operands/1/rewrites").isMissingNode());
+ }
+
+ @Test
+ public void testFoundryBaseRewrite () throws KustvaktException {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[orth=laufen]", "poliqarp");
+ String result = handler.processQuery(s.toJSON(), new KorAPUser("test"));
+ JsonNode node = JsonUtils.readTree(result);
+ assertEquals(node.at("/query/wrap/@type").asText(), "koral:term");
+ assertFalse(node.at("/query/wrap/foundry").isMissingNode());
+ assertFalse(node.at("/query/wrap/rewrites").isMissingNode());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/rewrite/IdRewriteTest.java b/src/test/java/de/ids_mannheim/korap/rewrite/IdRewriteTest.java
new file mode 100644
index 0000000..ef33982
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/rewrite/IdRewriteTest.java
@@ -0,0 +1,53 @@
+package de.ids_mannheim.korap.rewrite;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.rewrite.IdWriter;
+import de.ids_mannheim.korap.rewrite.RewriteHandler;
+import de.ids_mannheim.korap.user.KorAPUser;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author hanl
+ * @date 21/10/2015
+ */
+public class IdRewriteTest extends SpringJerseyTest {
+
+ @Autowired
+ private KustvaktConfiguration config;
+
+ @Test
+ public void insertTokenId () throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ assertTrue(handler.add(IdWriter.class));
+ String query = "[surface=Wort]";
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery(query, "poliqarp");
+ String value = handler.processQuery(s.toJSON(), new KorAPUser());
+ JsonNode result = JsonUtils.readTree(value);
+ assertNotNull(result);
+ assertTrue(result.path("query").has("idn"));
+ }
+
+ @Test
+ public void testIdWriterTest () throws KustvaktException {
+ RewriteHandler handler = new RewriteHandler(config);
+ assertTrue(handler.add(IdWriter.class));
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[base=Haus]", "poliqarp");
+ String result = handler.processQuery(s.toJSON(), new KorAPUser());
+ JsonNode node = JsonUtils.readTree(result);
+ assertNotNull(node);
+ assertFalse(node.at("/query/wrap").isMissingNode());
+ assertFalse(node.at("/query/idn").isMissingNode());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/rewrite/QueryRewriteTest.java b/src/test/java/de/ids_mannheim/korap/rewrite/QueryRewriteTest.java
new file mode 100644
index 0000000..524dad5
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/rewrite/QueryRewriteTest.java
@@ -0,0 +1,59 @@
+package de.ids_mannheim.korap.rewrite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * @author diewald
+ */
+public class QueryRewriteTest extends SpringJerseyTest {
+
+ @Test
+ public void testRewriteRefNotFound () throws KustvaktException, Exception {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "{q}").queryParam("ql", "poliqarp")
+ .resolveTemplate("q", "[orth=der]{#examplequery} Baum")
+ .request().get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Query system/examplequery is not found.");
+ }
+
+ @Test
+ public void testRewriteSystemQuery () throws KustvaktException, Exception {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "{q}").queryParam("ql", "poliqarp")
+ .resolveTemplate("q", "[orth=der]{#system-q} Baum").request()
+ .get();
+ String ent = response.readEntity(String.class);
+ // System.out.println(ent);
+ JsonNode node = JsonUtils.readTree(ent);
+ }
+
+ @Test
+ public void testRewriteRefRewrite () throws KustvaktException, Exception {
+ // Added in the database migration sql for tests
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "{q}").queryParam("ql", "poliqarp")
+ .resolveTemplate("q", "[orth=der]{#dory/dory-q} Baum").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/query/operands/1/@type").asText(),
+ "koral:token");
+ assertEquals(node.at("/query/operands/1/rewrites/0/scope").asText(),
+ "@type(koral:queryRef)");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/rewrite/ResultRewriteTest.java b/src/test/java/de/ids_mannheim/korap/rewrite/ResultRewriteTest.java
new file mode 100644
index 0000000..77767a0
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/rewrite/ResultRewriteTest.java
@@ -0,0 +1,31 @@
+package de.ids_mannheim.korap.rewrite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.config.TestVariables;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.rewrite.CollectionRewrite;
+import de.ids_mannheim.korap.rewrite.RewriteHandler;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author hanl
+ * @date 12/11/2015
+ */
+public class ResultRewriteTest extends SpringJerseyTest {
+
+ @Autowired
+ public RewriteHandler ha;
+
+ @Test
+ public void testPostRewriteNothingToDo () throws KustvaktException {
+ assertEquals(true, ha.add(CollectionRewrite.class),
+ "Handler could not be added to rewrite handler instance!");
+ String v = ha.processResult(TestVariables.RESULT, null);
+ assertEquals(JsonUtils.readTree(TestVariables.RESULT),
+ JsonUtils.readTree(v), "results do not match");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/rewrite/RewriteHandlerTest.java b/src/test/java/de/ids_mannheim/korap/rewrite/RewriteHandlerTest.java
new file mode 100644
index 0000000..35df9d3
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/rewrite/RewriteHandlerTest.java
@@ -0,0 +1,156 @@
+package de.ids_mannheim.korap.rewrite;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * EM: to do: Fix tests
+ * New DB does not save users.
+ *
+ * @author hanl
+ * @date 21/10/2015
+ */
+// @Deprecated
+// @Test
+// public void testRewriteNoBeanInject () throws KustvaktException {
+// RewriteHandler handler = new RewriteHandler(config);
+// QuerySerializer s = new QuerySerializer();
+// s.setQuery("[(base=laufen | base=gehen) & tt/pos=VVFIN]", "poliqarp");
+// assertTrue(handler.add(FoundryInject.class));
+// String res = handler.processQuery(s.toJSON(), null);
+// assertNotNull(res);
+// }
+//
+// @Deprecated
+// @Test
+// public void testRewriteBeanInject () throws KustvaktException {
+// RewriteHandler handler = new RewriteHandler(config);
+// QuerySerializer s = new QuerySerializer();
+// s.setQuery("[base=laufen | tt/pos=VVFIN]", "poliqarp");
+// assertTrue(handler.add(FoundryInject.class));
+// String res = handler.processQuery(s.toJSON(), null);
+// JsonNode node = JsonUtils.readTree(res);
+// assertNotNull(node);
+// assertEquals("tt", node.at("/query/wrap/operands/0/foundry")
+// .asText());
+// assertEquals("tt", node.at("/query/wrap/operands/1/foundry")
+// .asText());
+// }
+// EM: Fix me usersetting
+// @Test
+// @Ignore
+// public void testRewriteUserSpecific () throws KustvaktException {
+// RewriteHandler handler = new RewriteHandler(config);
+// QuerySerializer s = new QuerySerializer();
+// s.setQuery("[base=laufen|tt/pos=VFIN]", "poliqarp");
+// assertTrue(handler.add(FoundryInject.class));
+// String res = handler.processQuery(s.toJSON(), helper().getUser());
+// JsonNode node = JsonUtils.readTree(res);
+// assertNotNull(node);
+// assertEquals("tt_test",
+// node.at("/query/wrap/operands/0/foundry").asText());
+// assertNotEquals("tt_test",
+// node.at("/query/wrap/operands/1/foundry").asText());
+// }
+// EM: Fix me usersetting
+// @Override
+// public void initMethod () throws KustvaktException {
+// helper().setupAccount();
+// UserDataDbIface settingsdao = BeansFactory.getTypeFactory()
+// .getTypeInterfaceBean(
+// helper().getContext().getUserDataProviders(),
+// UserSettings.class);
+// assertNotNull(settingsdao);
+// UserSettings s = (UserSettings) settingsdao.get(helper().getUser());
+// s.setField(Attributes.DEFAULT_LEMMA_FOUNDRY, "tt_test");
+// settingsdao.update(s);
+// }
+public class RewriteHandlerTest extends SpringJerseyTest {
+
+ @Autowired
+ private KustvaktConfiguration config;
+
+ @Test
+ public void testRewriteTaskAdd () {
+ RewriteHandler handler = new RewriteHandler(config);
+ assertTrue(handler.add(FoundryInject.class));
+ assertTrue(handler.add(CollectionCleanRewrite.class));
+ assertTrue(handler.add(IdWriter.class));
+ }
+
+ // throws exception cause of missing configuration
+ @Test
+ public void testRewriteConfigThrowsException () {
+ assertThrows(RuntimeException.class, () -> {
+ RewriteHandler handler = new RewriteHandler();
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[(base=laufen | base=gehen) & tt/pos=VVFIN]",
+ "poliqarp");
+ assertTrue(handler.add(FoundryInject.class));
+ handler.processQuery(s.toJSON(), null);
+ });
+ }
+ // @Deprecated
+ // @Test
+ // public void testRewriteNoBeanInject () throws KustvaktException {
+ // RewriteHandler handler = new RewriteHandler(config);
+ // QuerySerializer s = new QuerySerializer();
+ // s.setQuery("[(base=laufen | base=gehen) & tt/pos=VVFIN]", "poliqarp");
+ // assertTrue(handler.add(FoundryInject.class));
+ // String res = handler.processQuery(s.toJSON(), null);
+ // assertNotNull(res);
+ // }
+ //
+ // @Deprecated
+ // @Test
+ // public void testRewriteBeanInject () throws KustvaktException {
+ // RewriteHandler handler = new RewriteHandler(config);
+ // QuerySerializer s = new QuerySerializer();
+ // s.setQuery("[base=laufen | tt/pos=VVFIN]", "poliqarp");
+ // assertTrue(handler.add(FoundryInject.class));
+ // String res = handler.processQuery(s.toJSON(), null);
+ // JsonNode node = JsonUtils.readTree(res);
+ // assertNotNull(node);
+ // assertEquals("tt", node.at("/query/wrap/operands/0/foundry")
+ // .asText());
+ // assertEquals("tt", node.at("/query/wrap/operands/1/foundry")
+ // .asText());
+ // }
+ // EM: Fix me usersetting
+ // @Test
+ // @Ignore
+ // public void testRewriteUserSpecific () throws KustvaktException {
+ // RewriteHandler handler = new RewriteHandler(config);
+ // QuerySerializer s = new QuerySerializer();
+ // s.setQuery("[base=laufen|tt/pos=VFIN]", "poliqarp");
+ // assertTrue(handler.add(FoundryInject.class));
+ // String res = handler.processQuery(s.toJSON(), helper().getUser());
+ // JsonNode node = JsonUtils.readTree(res);
+ // assertNotNull(node);
+ // assertEquals("tt_test",
+ // node.at("/query/wrap/operands/0/foundry").asText());
+ // assertNotEquals("tt_test",
+ // node.at("/query/wrap/operands/1/foundry").asText());
+ // }
+ // EM: Fix me usersetting
+ // @Override
+ // public void initMethod () throws KustvaktException {
+ // helper().setupAccount();
+ // UserDataDbIface settingsdao = BeansFactory.getTypeFactory()
+ // .getTypeInterfaceBean(
+ // helper().getContext().getUserDataProviders(),
+ // UserSettings.class);
+ // assertNotNull(settingsdao);
+ // UserSettings s = (UserSettings) settingsdao.get(helper().getUser());
+ // s.setField(Attributes.DEFAULT_LEMMA_FOUNDRY, "tt_test");
+ // settingsdao.update(s);
+ // }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/rewrite/VirtualCorpusRewriteTest.java b/src/test/java/de/ids_mannheim/korap/rewrite/VirtualCorpusRewriteTest.java
new file mode 100644
index 0000000..f0ea66c
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/rewrite/VirtualCorpusRewriteTest.java
@@ -0,0 +1,133 @@
+package de.ids_mannheim.korap.rewrite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.cache.VirtualCorpusCache;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.NamedVCLoader;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.dao.QueryDao;
+import de.ids_mannheim.korap.entity.QueryDO;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.util.QueryException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * @author margaretha
+ */
+public class VirtualCorpusRewriteTest extends SpringJerseyTest {
+
+ @Autowired
+ private NamedVCLoader vcLoader;
+
+ @Autowired
+ private QueryDao dao;
+
+ @Test
+ public void testRefCachedVC ()
+ throws KustvaktException, IOException, QueryException {
+ vcLoader.loadVCToCache("named-vc1", "/vc/named-vc1.jsonld");
+ assertTrue(VirtualCorpusCache.contains("named-vc1"));
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo named-vc1").request().get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ node = node.at("/collection");
+ assertEquals(node.at("/@type").asText(), "koral:docGroup");
+ assertTrue(node.at("/operands/1/rewrites").isMissingNode());
+ testRefCachedVCWithUsername();
+ QueryDO vc = dao.retrieveQueryByName("named-vc1", "system");
+ dao.deleteQuery(vc);
+ vc = dao.retrieveQueryByName("named-vc1", "system");
+ assertNull(vc);
+ VirtualCorpusCache.delete("named-vc1");
+ assertFalse(VirtualCorpusCache.contains("named-vc1"));
+ }
+
+ private void testRefCachedVCWithUsername ()
+ throws KustvaktException, IOException, QueryException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"system/named-vc1\"").request()
+ .get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ node = node.at("/collection");
+ assertEquals(node.at("/@type").asText(), "koral:docGroup");
+ node = node.at("/operands/1/rewrites");
+ assertEquals(2, node.size());
+ assertEquals(node.at("/0/operation").asText(), "operation:deletion");
+ assertEquals(node.at("/1/operation").asText(), "operation:insertion");
+ }
+
+ @Test
+ public void testRewriteFreeAndSystemVCRef ()
+ throws KustvaktException, Exception {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"system-vc\"").request().get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ node = node.at("/collection");
+ assertEquals(node.at("/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/operands/0/@type").asText(), "koral:doc");
+ assertEquals(node.at("/operands/1/@type").asText(), "koral:doc");
+ assertEquals(node.at("/operands/1/value").asText(), "GOE");
+ assertEquals(node.at("/operands/1/key").asText(), "corpusSigle");
+ node = node.at("/operands/1/rewrites");
+ assertEquals(3, node.size());
+ assertEquals(node.at("/0/operation").asText(), "operation:deletion");
+ assertEquals(node.at("/1/operation").asText(), "operation:deletion");
+ assertEquals(node.at("/2/operation").asText(), "operation:insertion");
+ }
+
+ @Test
+ public void testRewritePubAndSystemVCRef () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"system/system-vc\"").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("user", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ node = node.at("/collection");
+ assertEquals(node.at("/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/operands/0/@type").asText(), "koral:docGroup");
+ node = node.at("/operands/1/rewrites");
+ assertEquals(3, node.size());
+ assertEquals(node.at("/0/operation").asText(), "operation:deletion");
+ assertEquals(node.at("/1/operation").asText(), "operation:deletion");
+ assertEquals(node.at("/2/operation").asText(), "operation:insertion");
+ }
+
+ @Test
+ public void testRewriteWithDoryVCRef ()
+ throws KustvaktException, IOException, QueryException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Fisch").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"dory/dory-vc\"").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ node = node.at("/collection");
+ assertEquals(node.at("/@type").asText(), "koral:docGroup");
+ node = node.at("/operands/1/rewrites");
+ assertEquals(3, node.size());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/scenario/ICCTest.java b/src/test/java/de/ids_mannheim/korap/scenario/ICCTest.java
new file mode 100644
index 0000000..fcc655f
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/scenario/ICCTest.java
@@ -0,0 +1,190 @@
+package de.ids_mannheim.korap.scenario;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.test.context.ContextConfiguration;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+/**
+ * <p>Test scenario for ICC (International Comparable Corpus)
+ * instance</p>
+ * <p>
+ * The instance requires user authentication and access to data is
+ * restricted to only logged-in users.
+ * <p>
+ * This class uses <em>test-config-icc.xml</em> spring XML config
+ * defining the location of a specific kustvakt configuration file for
+ * this instance:<em>kustvakt-icc.conf</em>.
+ *
+ * <p>
+ * To run a Kustvakt jar with ICC setup, the following files are
+ * needed:
+ * </p>
+ * <ul>
+ * <li>a Spring configuration file</li>
+ * <li>a Kustvakt configuration file that must be placed at the jar
+ * folder</li>
+ * <li>a JDBC properties file that must be placed at the jar
+ * folder</li>
+ * </ul>
+ * <p>
+ * Example:
+ *
+ * <p>
+ * <code>
+ * java -jar Kustvakt-full-0.69.3.jar --spring-config
+ * test-config-icc.xml
+ * </code>
+ * </p>
+ *
+ * <h1>Spring configuration file</h1>
+ * <p>
+ * For ICC, collectionRewrite in the Spring XML configuration must
+ * be disabled. This has been done in <em>test-config-icc.xml</em>.
+ * </p>
+ *
+ * <p>For testing, the ICC configuration uses HTTP Basic
+ * Authentication and doesn't use LDAP.</p>
+ *
+ * <p>For production, Basic Authentication must be
+ * disabled/commented.</p>
+ *
+ * <pre><code>
+ * <bean id="basic_auth"
+ * class="de.ids_mannheim.korap.authentication.BasicAuthentication"/>
+ *
+ * <util:list id="kustvakt_authproviders"
+ * value-type="de.ids_mannheim.korap.interfaces.AuthenticationIface">
+ * <!-- <ref bean="basic_auth" /> -->
+ * </code>
+ * </pre>
+ *
+ * <p>For production, the init-method of Initializator should be
+ * changed to init.</p>
+ *
+ * <pre>
+ * <code>
+ * <bean id="initializator"
+ * class="de.ids_mannheim.de.init.Initializator"
+ * init-method="init"></bean>
+ * </code>
+ * </pre>
+ *
+ * <h1>Kustvakt configuration file</h1>
+ *
+ * <p>
+ * The configuration file: <em>kustvakt-icc.conf</em> includes the
+ * following setup:
+ * </p>
+ *
+ * <ul>
+ * <li>
+ * <em>krill.indexDir</em> should indicate the location of the index.
+ * It is set to the wiki-index for the test.
+ * </li>
+ *
+ * <p>
+ * <code>krill.indexDir=../wiki-index</code>
+ * </p>
+ *
+ * <li>
+ * <em>availability.regex</em>
+ * properties should be removed or commented since the data doesn't
+ * contain availability and access to data is not determined by this
+ * field.
+ * </li>
+ *
+ * <li>
+ * Resource filter class names for the search and match info services
+ * should be defined by <em>search.resource.filters property</em>. For
+ * example, to restricts access with only authentication filter:</li>
+ *
+ * <p>
+ * <code>search.resource.filters=AuthenticationFilter </code>
+ * </p>
+ *
+ * <li><em>oauth2.password.authentication</em> indicating the
+ * authentication method to match usernames and password.
+ * <code>TEST</code> is a dummy authentication that doesn't do any
+ * matching. For production, it must be changed to
+ * <code>LDAP</code>.</li>
+ *
+ * <p><code>oauth2.password.authentication=LDAP</code></p>
+ *
+ * </ul>
+ *
+ * @author elma
+ * @see /src/main/resources/properties/jdbc.properties
+ */
+@ContextConfiguration("classpath:test-config-icc.xml")
+public class ICCTest extends SpringJerseyTest {
+
+ public final static String API_VERSION = "v1.0";
+
+ public String basicAuth;
+
+ public ICCTest () throws KustvaktException {
+ basicAuth = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("user", "password");
+ }
+
+ @Test
+ public void searchWithoutLogin () throws KustvaktException {
+ Response r = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .request().get();
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), r.getStatus());
+ String entity = r.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void searchWithLogin () throws KustvaktException {
+ Response r = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .request().header(Attributes.AUTHORIZATION, basicAuth).get();
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+ String entity = r.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertTrue(node.at("/matches").size() > 0);
+ }
+
+ @Test
+ public void matchInfoWithoutLogin () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("WDD17").path("982").path("72848").path("p2815-2816")
+ .queryParam("foundry", "*").request().get();
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void matchInfoWithLogin () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("WDD17").path("982").path("72848").path("p2815-2816")
+ .queryParam("foundry", "*").request()
+ .header(Attributes.AUTHORIZATION, basicAuth).get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertTrue(node.at("/hasSnippet").asBoolean());
+ assertNotNull(node.at("/matchID").asText());
+ assertNotNull(node.at("/snippet").asText());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/server/EmbeddedLdapServerTest.java b/src/test/java/de/ids_mannheim/korap/server/EmbeddedLdapServerTest.java
new file mode 100644
index 0000000..98b35cf
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/server/EmbeddedLdapServerTest.java
@@ -0,0 +1,107 @@
+package de.ids_mannheim.korap.server;
+
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.util.Base64;
+import de.ids_mannheim.korap.authentication.LdapAuth3;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Test;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import static de.ids_mannheim.korap.authentication.LdapAuth3.LDAP_AUTH_ROK;
+import static de.ids_mannheim.korap.authentication.LdapAuth3.LDAP_AUTH_RUNKNOWN;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class EmbeddedLdapServerTest {
+
+ public static final String TEST_EMBEDDED_LDAP_CONF = "src/test/resources/test-embedded-ldap.conf";
+
+ @AfterAll
+ static void shutdownEmbeddedLdapServer () {
+ EmbeddedLdapServer.stop();
+ }
+
+ @Test
+ public void embeddedServerStartsAutomaticallyAndUsersCanLogin ()
+ throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("user", "password", TEST_EMBEDDED_LDAP_CONF));
+ }
+
+ @Test
+ public void usersWithClearPasswordCanLogin () throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("user1", "password1", TEST_EMBEDDED_LDAP_CONF));
+ }
+
+ @Test
+ public void usersWithSHA1PasswordCanLogin ()
+ throws LDAPException, NoSuchAlgorithmException {
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("user3", "password3", TEST_EMBEDDED_LDAP_CONF));
+ }
+
+ @Test
+ public void usersWithSHA256PasswordCanLogin () throws LDAPException,
+ NoSuchAlgorithmException, InvalidKeySpecException {
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("user4", "password4", TEST_EMBEDDED_LDAP_CONF));
+ }
+
+ @Test
+ public void asteriskPasswordsFail () throws LDAPException {
+ assertEquals(LDAP_AUTH_RUNKNOWN,
+ LdapAuth3.login("user1", "*", TEST_EMBEDDED_LDAP_CONF));
+ }
+
+ @Test
+ public void loginWithPreencodedPBKDF2Password () throws LDAPException,
+ NoSuchAlgorithmException, InvalidKeySpecException {
+ byte[] salt = new byte[32];
+ KeySpec spec = new PBEKeySpec("password5".toCharArray(), salt, 65536,
+ 256);
+ SecretKeyFactory f = SecretKeyFactory
+ .getInstance("PBKDF2withHmacSHA256");
+ byte[] hash = f.generateSecret(spec).getEncoded();
+ final String pbkdf2sha256Password = "{PBKDF2-SHA256}"
+ + Base64.encode(hash);
+ // System.out.println(pbkdf2sha256Password);
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("user5",
+ pbkdf2sha256Password, TEST_EMBEDDED_LDAP_CONF));
+ }
+
+ @Test
+ public void loginWithUnencodedPBKDF2PasswordFails () throws LDAPException,
+ NoSuchAlgorithmException, InvalidKeySpecException {
+ assertEquals(LDAP_AUTH_RUNKNOWN,
+ LdapAuth3.login("user5", "password5", TEST_EMBEDDED_LDAP_CONF));
+ }
+
+ @Test
+ public void unauthorizedUsersAreNotAllowed () throws LDAPException {
+ assertEquals(LDAP_AUTH_RUNKNOWN,
+ LdapAuth3.login("yuser", "password", TEST_EMBEDDED_LDAP_CONF));
+ }
+
+ @Test
+ public void gettingMailForUser () throws LDAPException,
+ UnknownHostException, GeneralSecurityException {
+ EmbeddedLdapServer.startIfNotRunning(TEST_EMBEDDED_LDAP_CONF);
+ assertEquals(LdapAuth3.getEmail("user2", TEST_EMBEDDED_LDAP_CONF),
+ "user2@example.com");
+ }
+
+ @Test
+ public void gettingMailForNAUTHUserIsNull () throws LDAPException,
+ UnknownHostException, GeneralSecurityException {
+ EmbeddedLdapServer.startIfNotRunning(TEST_EMBEDDED_LDAP_CONF);
+ assertEquals(null,
+ LdapAuth3.getEmail("user1000", TEST_EMBEDDED_LDAP_CONF));
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/service/VirtualCorpusServiceTest.java b/src/test/java/de/ids_mannheim/korap/service/VirtualCorpusServiceTest.java
new file mode 100644
index 0000000..8799ad7
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/service/VirtualCorpusServiceTest.java
@@ -0,0 +1,125 @@
+package de.ids_mannheim.korap.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import de.ids_mannheim.korap.constant.QueryType;
+import de.ids_mannheim.korap.constant.ResourceType;
+import de.ids_mannheim.korap.constant.UserGroupStatus;
+import de.ids_mannheim.korap.dto.QueryAccessDto;
+import de.ids_mannheim.korap.dto.QueryDto;
+import de.ids_mannheim.korap.entity.QueryDO;
+import de.ids_mannheim.korap.entity.UserGroup;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.web.input.QueryJson;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration("classpath:test-config.xml")
+public class VirtualCorpusServiceTest {
+
+ @Autowired
+ private QueryService vcService;
+
+ @Autowired
+ private UserGroupService groupService;
+
+ @Test
+ public void testCreateNonUniqueVC () throws KustvaktException {
+ // EM: message differs depending on the database used
+ // for testing. The message below is from sqlite.
+ // thrown.expectMessage("A UNIQUE constraint failed "
+ // + "(UNIQUE constraint failed: virtual_corpus.name, "
+ // + "virtual_corpus.created_by)");
+ QueryJson vc = new QueryJson();
+ vc.setCorpusQuery("corpusSigle=GOE");
+ vc.setType(ResourceType.PRIVATE);
+ vc.setQueryType(QueryType.VIRTUAL_CORPUS);
+ assertThrows(KustvaktException.class,
+ () -> vcService.storeQuery(vc, "dory-vc", "dory", "dory"));
+ }
+
+ @Test
+ public void createDeletePublishVC () throws KustvaktException {
+ String vcName = "new-published-vc";
+ QueryJson vc = new QueryJson();
+ vc.setCorpusQuery("corpusSigle=GOE");
+ vc.setType(ResourceType.PUBLISHED);
+ vc.setQueryType(QueryType.VIRTUAL_CORPUS);
+ String username = "VirtualCorpusServiceTest";
+ vcService.storeQuery(vc, vcName, username, username);
+ List<QueryAccessDto> accesses = vcService
+ .listQueryAccessByUsername("admin");
+ int size = accesses.size();
+ QueryAccessDto dto = accesses.get(accesses.size() - 1);
+ assertEquals(vcName, dto.getQueryName());
+ assertEquals(dto.getCreatedBy(), "system");
+ assertTrue(dto.getUserGroupName().startsWith("auto"));
+ // check hidden group
+ int groupId = dto.getUserGroupId();
+ UserGroup group = groupService.retrieveUserGroupById(groupId);
+ assertEquals(UserGroupStatus.HIDDEN, group.getStatus());
+ // delete vc
+ vcService.deleteQueryByName(username, vcName, username,
+ QueryType.VIRTUAL_CORPUS);
+ // check hidden access
+ accesses = vcService.listQueryAccessByUsername("admin");
+ assertEquals(size - 1, accesses.size());
+ // check hidden group
+ KustvaktException e = assertThrows(KustvaktException.class,
+ () -> groupService.retrieveUserGroupById(groupId));
+ assertEquals("Group with id " + groupId + " is not found",
+ e.getMessage());
+ }
+
+ @Test
+ public void testEditPublishVC () throws KustvaktException {
+ String username = "dory";
+ int vcId = 2;
+ String vcName = "group-vc";
+ QueryDO existingVC = vcService.searchQueryByName(username, vcName,
+ username, QueryType.VIRTUAL_CORPUS);
+ QueryJson vcJson = new QueryJson();
+ vcJson.setType(ResourceType.PUBLISHED);
+ vcService.editQuery(existingVC, vcJson, vcName, username);
+ // check VC
+ QueryDto vcDto = vcService.searchQueryById("dory", vcId);
+ assertEquals(vcName, vcDto.getName());
+ assertEquals(ResourceType.PUBLISHED.displayName(), vcDto.getType());
+ // check access
+ List<QueryAccessDto> accesses = vcService
+ .listQueryAccessByUsername("admin");
+ int size = accesses.size();
+ QueryAccessDto dto = accesses.get(accesses.size() - 1);
+ assertEquals(vcName, dto.getQueryName());
+ assertEquals(dto.getCreatedBy(), "system");
+ assertTrue(dto.getUserGroupName().startsWith("auto"));
+ // check auto hidden group
+ int groupId = dto.getUserGroupId();
+ UserGroup group = groupService.retrieveUserGroupById(groupId);
+ assertEquals(UserGroupStatus.HIDDEN, group.getStatus());
+ // 2nd edit (withdraw from publication)
+ vcJson = new QueryJson();
+ vcJson.setType(ResourceType.PROJECT);
+ vcService.editQuery(existingVC, vcJson, vcName, username);
+ // check VC
+ vcDto = vcService.searchQueryById("dory", vcId);
+ assertEquals(vcDto.getName(), "group-vc");
+ assertEquals(ResourceType.PROJECT.displayName(), vcDto.getType());
+ // check access
+ accesses = vcService.listQueryAccessByUsername("admin");
+ assertEquals(size - 1, accesses.size());
+ KustvaktException e = assertThrows(KustvaktException.class,
+ () -> groupService.retrieveUserGroupById(groupId));
+ assertEquals("Group with id " + groupId + " is not found",
+ e.getMessage());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/user/DataFactoryTest.java b/src/test/java/de/ids_mannheim/korap/user/DataFactoryTest.java
new file mode 100644
index 0000000..5584fb7
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/user/DataFactoryTest.java
@@ -0,0 +1,156 @@
+package de.ids_mannheim.korap.user;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * Taken from UserdataTest
+ *
+ * @author hanl
+ * @date 27/01/2016
+ */
+public class DataFactoryTest {
+
+ @Test
+ public void testDataFactoryAdd () throws KustvaktException {
+ String data = "{}";
+ Object node = JsonUtils.readTree(data);
+ DataFactory factory = DataFactory.getFactory();
+ assertTrue(factory.addValue(node, "field_1", "value_1"));
+ assertTrue(factory.addValue(node, "field_2", 20));
+ assertTrue(factory.addValue(node, "field_3", true));
+ data = "[]";
+ node = JsonUtils.readTree(data);
+ factory = DataFactory.getFactory();
+ assertTrue(factory.addValue(node, "field_1", "value_1"));
+ assertTrue(factory.addValue(node, "field_2", 20));
+ assertTrue(factory.addValue(node, "field_3", true));
+ }
+
+ @Test
+ public void testDataFactoryGet () throws KustvaktException {
+ String data = "{}";
+ Object node = JsonUtils.readTree(data);
+ DataFactory factory = DataFactory.getFactory();
+ assertTrue(factory.addValue(node, "field_1", "value_1"));
+ assertTrue(factory.addValue(node, "field_2", 20));
+ assertTrue(factory.addValue(node, "field_3", true));
+ Object value = factory.getValue(node, "field_1");
+ assertEquals(value, "value_1");
+ value = factory.getValue(node, "field_2");
+ assertEquals(20, value);
+ value = factory.getValue(node, "field_3");
+ assertEquals(true, value);
+ data = "[]";
+ node = JsonUtils.readTree(data);
+ assertTrue(factory.addValue(node, "", "value_2"));
+ assertTrue(factory.addValue(node, "", 10));
+ assertTrue(factory.addValue(node, "", false));
+ value = factory.getValue(node, "/0");
+ assertEquals(value, "value_2");
+ value = factory.getValue(node, "/1");
+ assertEquals(10, value);
+ value = factory.getValue(node, "/2");
+ assertEquals(false, value);
+ }
+
+ @Test
+ public void testDataFactoryMerge () throws KustvaktException {
+ String data = "{}";
+ Object node = JsonUtils.readTree(data);
+ DataFactory factory = DataFactory.getFactory();
+ assertTrue(factory.addValue(node, "field_1", "value_1"));
+ assertTrue(factory.addValue(node, "field_2", 20));
+ assertTrue(factory.addValue(node, "field_3", true));
+ data = "{}";
+ Object node2 = JsonUtils.readTree(data);
+ assertTrue(factory.addValue(node2, "field_1", "value_new"));
+ assertTrue(factory.addValue(node2, "field_2", "value_next"));
+ assertTrue(factory.addValue(node2, "field_4", "value_2"));
+ assertTrue(factory.addValue(node2, "field_7", "value_3"));
+ JsonNode node_new = (JsonNode) factory.merge(node, node2);
+ assertEquals(node_new.path("field_1").asText(), "value_new");
+ assertEquals(node_new.path("field_2").asText(), "value_next");
+ assertEquals(true, node_new.path("field_3").asBoolean());
+ assertEquals(node_new.path("field_4").asText(), "value_2");
+ assertEquals(node_new.path("field_7").asText(), "value_3");
+ }
+
+ @Test
+ public void testDataFactoryKeys () throws KustvaktException {
+ String data = "{}";
+ Object node = JsonUtils.readTree(data);
+ DataFactory factory = DataFactory.getFactory();
+ assertTrue(factory.addValue(node, "field_1", "value_1"));
+ assertTrue(factory.addValue(node, "field_2", 20));
+ assertTrue(factory.addValue(node, "field_3", true));
+ assertEquals(3, factory.size(node));
+ assertEquals(3, factory.keys(node).size());
+ }
+
+ @Test
+ @Disabled
+ public void testDataFactoryRemove () throws KustvaktException {
+ String data = "{}";
+ Object node = JsonUtils.readTree(data);
+ DataFactory factory = DataFactory.getFactory();
+ assertTrue(factory.addValue(node, "field_1", "value_1"));
+ assertTrue(factory.addValue(node, "field_2", 20));
+ assertTrue(factory.addValue(node, "field_3", true));
+ Object value = factory.getValue(node, "field_1");
+ assertEquals(value, "value_1");
+ value = factory.getValue(node, "field_2");
+ assertEquals(20, value);
+ value = factory.getValue(node, "field_3");
+ assertEquals(true, value);
+ assertTrue(factory.removeValue(node, "field_1"));
+ assertTrue(factory.removeValue(node, "field_2"));
+ assertTrue(factory.removeValue(node, "field_3"));
+ assertNotNull(node);
+ assertEquals(node.toString(), "{}");
+ data = "[]";
+ node = JsonUtils.readTree(data);
+ assertTrue(factory.addValue(node, "", "value_2"));
+ assertTrue(factory.addValue(node, "", 10));
+ assertTrue(factory.addValue(node, "", false));
+ value = factory.getValue(node, "/0");
+ assertEquals(value, "value_2");
+ value = factory.getValue(node, "/1");
+ assertEquals(10, value);
+ value = factory.getValue(node, "/2");
+ assertEquals(false, value);
+ // fixme: cannot be removed
+ assertTrue(factory.removeValue(node, "0"));
+ assertTrue(factory.removeValue(node, "1"));
+ assertTrue(factory.removeValue(node, "2"));
+ assertNotNull(node);
+ assertEquals(node.toString(), "[]");
+ }
+
+ @Test
+ public void testDataFactoryEmbeddedProperty () throws KustvaktException {
+ String data = "{}";
+ JsonNode node = JsonUtils.readTree(data);
+ DataFactory factory = DataFactory.getFactory();
+ assertTrue(factory.addValue(node, "field_1", "value_1"));
+ assertTrue(factory.addValue(node, "field_2", 20));
+ assertTrue(factory.addValue(node, "field_3", true));
+ ArrayNode array = JsonUtils.createArrayNode();
+ array.add(10);
+ array.add("v1");
+ array.add("v2");
+ factory.addValue(node, "field_3", array);
+ assertNotNull(node);
+ assertEquals(10, node.at("/field_3/0").asInt());
+ assertEquals(node.at("/field_3/1").asText(), "v1");
+ assertEquals(node.at("/field_3/2").asText(), "v2");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/user/UserdataTest.java b/src/test/java/de/ids_mannheim/korap/user/UserdataTest.java
new file mode 100644
index 0000000..0e99a7f
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/user/UserdataTest.java
@@ -0,0 +1,171 @@
+package de.ids_mannheim.korap.user;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.validator.ApacheValidator;
+import edu.emory.mathcs.backport.java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * @author hanl, margaretha
+ * @date 27/01/2016
+ */
+public class UserdataTest {
+
+ // EM: added
+ @Test
+ public void testReadEmptyMap () throws KustvaktException {
+ Userdata userData = new UserSettingProcessor();
+ userData.read(new HashMap<>(), false);
+ String jsonSettings = userData.serialize();
+ assertEquals(jsonSettings, "{}");
+ }
+
+ @Test
+ public void testReadNullMap () throws KustvaktException {
+ Userdata userData = new UserSettingProcessor();
+ userData.read(null, false);
+ String jsonSettings = userData.serialize();
+ assertEquals(jsonSettings, "{}");
+ }
+
+ // EM: based on MH code, supposedly to validate entries like email
+ // and date. See ApacheValidator
+ //
+ // It has inconsistent behaviors:
+ // throws exceptions when there are invalid entries in a list,
+ // otherwise skips invalid entries and returns a valid map
+ // Moreover, Userdata.validate(ValidatorIface) does not return a
+ // valid map.
+ //
+ // At the moment, validation is not needed for default settings.
+ @Test
+ public void testValidateMap () throws IOException, KustvaktException {
+ Map<String, Object> map = new HashMap<>();
+ map.put("k1", Arrays.asList(new String[] { "a", "b", "c" }));
+ map.put("k2", Arrays.asList(new Integer[] { 1, 2, 3 }));
+ Userdata data = new UserSettingProcessor();
+ data.read(map, false);
+ data.validate(new ApacheValidator());
+ }
+
+ // EM: below are tests from MH
+ @Test
+ public void testDataValidation () {
+ Userdata data = new UserDetails(1);
+ data.setField(Attributes.COUNTRY, "Germany");
+ String[] req = data.requiredFields();
+ String[] r = data.findMissingFields();
+ assertNotEquals(0, r.length);
+ assertEquals(req.length, r.length);
+ assertFalse(data.isValid());
+ }
+
+ @Test
+ public void testSettingsValidation () {
+ Userdata data = new UserSettingProcessor();
+ data.setField(Attributes.FILE_FORMAT_FOR_EXPORT, "export");
+ String[] req = data.requiredFields();
+ String[] r = data.findMissingFields();
+ assertEquals(0, r.length);
+ assertEquals(req.length, r.length);
+ assertTrue(data.isValid());
+ }
+
+ @Test
+ public void testUserdataRequiredFields () throws KustvaktException {
+ UserDetails details = new UserDetails(-1);
+ Map<String, Object> m = new HashMap<>();
+ m.put(Attributes.FIRSTNAME, "first");
+ m.put(Attributes.LASTNAME, "last");
+ m.put(Attributes.ADDRESS, "address");
+ m.put(Attributes.EMAIL, "email");
+ details.setData(JsonUtils.toJSON(m));
+ details.setData(JsonUtils.toJSON(m));
+ String[] missing = details.findMissingFields();
+ assertEquals(0, missing.length);
+ }
+
+ @Test
+ public void testUserdataDefaultFields () throws KustvaktException {
+ UserSettingProcessor settings = new UserSettingProcessor();
+ Map<String, Object> m = new HashMap<>();
+ m.put(Attributes.DEFAULT_FOUNDRY_RELATION, "rel_1");
+ m.put(Attributes.DEFAULT_FOUNDRY_CONSTITUENT, "const_1");
+ m.put(Attributes.DEFAULT_FOUNDRY_POS, "pos_1");
+ m.put(Attributes.DEFAULT_FOUNDRY_LEMMA, "lemma_1");
+ m.put(Attributes.PAGE_LENGTH, 10);
+ m.put(Attributes.QUERY_LANGUAGE, "poliqarp");
+ m.put(Attributes.METADATA_QUERY_EXPERT_MODUS, false);
+ settings.read(m, true);
+ assertNotEquals(m.size(), settings.size());
+ assertEquals(settings.defaultFields().length, settings.size());
+ assertEquals(settings.get(Attributes.DEFAULT_FOUNDRY_RELATION),
+ "rel_1");
+ assertEquals(settings.get(Attributes.DEFAULT_FOUNDRY_POS), "pos_1");
+ assertEquals(settings.get(Attributes.DEFAULT_FOUNDRY_LEMMA), "lemma_1");
+ assertEquals(settings.get(Attributes.DEFAULT_FOUNDRY_CONSTITUENT),
+ "const_1");
+ assertEquals(10, settings.get(Attributes.PAGE_LENGTH));
+ }
+
+ @Test
+ public void testUserDataRequiredFieldsException () {
+ assertThrows(KustvaktException.class, () -> {
+ UserDetails details = new UserDetails(-1);
+ Map<String, Object> m = new HashMap<>();
+ m.put(Attributes.FIRSTNAME, "first");
+ m.put(Attributes.LASTNAME, "last");
+ m.put(Attributes.ADDRESS, "address");
+ details.setData(JsonUtils.toJSON(m));
+ String[] missing = details.findMissingFields();
+ assertEquals(1, missing.length);
+ assertEquals(missing[0], "email");
+ details.checkRequired();
+ });
+ }
+
+ @Test
+ public void testUserDataPointerFunction () throws KustvaktException {
+ UserDetails details = new UserDetails(-1);
+ Map<String, Object> m = new HashMap<>();
+ m.put(Attributes.FIRSTNAME, "first");
+ m.put(Attributes.LASTNAME, "last");
+ m.put(Attributes.ADDRESS, "address");
+ m.put(Attributes.EMAIL, "email");
+ details.setData(JsonUtils.toJSON(m));
+ ArrayNode array = JsonUtils.createArrayNode();
+ array.add(100);
+ array.add("message");
+ details.setField("errors", array);
+ assertEquals(100, details.get("/errors/0"));
+ assertEquals(details.get("/errors/1"), "message");
+ }
+
+ @Test
+ public void testUserDataUpdate () {
+ UserDetails details = new UserDetails(-1);
+ details.setField(Attributes.FIRSTNAME, "first");
+ details.setField(Attributes.LASTNAME, "last");
+ details.setField(Attributes.ADDRESS, "address");
+ details.setField(Attributes.EMAIL, "email");
+ UserDetails details2 = new UserDetails(-1);
+ details2.setField(Attributes.COUNTRY, "Germany");
+ details.update(details2);
+ assertEquals(details.get(Attributes.FIRSTNAME), "first");
+ assertEquals(details.get(Attributes.COUNTRY), "Germany");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/ApiVersionTest.java b/src/test/java/de/ids_mannheim/korap/web/ApiVersionTest.java
new file mode 100644
index 0000000..9e138fa
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/ApiVersionTest.java
@@ -0,0 +1,39 @@
+package de.ids_mannheim.korap.web;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.net.URI;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * @author margaretha
+ */
+public class ApiVersionTest extends SpringJerseyTest {
+
+ @Test
+ public void testSearchWithoutVersion () throws KustvaktException {
+ Response response = target().path("api").path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .request().accept(MediaType.APPLICATION_JSON).get();
+ assertEquals(HttpStatus.PERMANENT_REDIRECT_308, response.getStatus());
+ URI location = response.getLocation();
+ assertEquals("/api/" + API_VERSION + "/search", location.getPath());
+ }
+
+ @Test
+ public void testSearchWrongVersion () throws KustvaktException {
+ Response response = target().path("api").path("v0.2").path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .request().accept(MediaType.APPLICATION_JSON).get();
+ assertEquals(HttpStatus.PERMANENT_REDIRECT_308, response.getStatus());
+ URI location = response.getLocation();
+ assertEquals("/api/" + API_VERSION + "/search", location.getPath());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/InitialSuperClientTest.java b/src/test/java/de/ids_mannheim/korap/web/InitialSuperClientTest.java
new file mode 100644
index 0000000..e8c8eef
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/InitialSuperClientTest.java
@@ -0,0 +1,69 @@
+package de.ids_mannheim.korap.web;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.oauth2.service.OAuth2InitClientService;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.controller.OAuth2TestBase;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+public class InitialSuperClientTest extends OAuth2TestBase {
+
+ @Autowired
+ private FullConfiguration config;
+
+ @Autowired
+ private OAuth2ClientDao clientDao;
+
+ private String path = KustvaktConfiguration.DATA_FOLDER + "/"
+ + OAuth2InitClientService.TEST_OUTPUT_FILENAME;
+
+ @Test
+ public void testCreatingInitialSuperClient ()
+ throws IOException, KustvaktException {
+ assertTrue(config.createInitialSuperClient());
+ File f = new File(path);
+ assertTrue(f.exists());
+ JsonNode node = JsonUtils.readFile(path, JsonNode.class);
+ String superClientId = node.at("/client_id").asText();
+ String superClientSecret = node.at("/client_secret").asText();
+ OAuth2Client superClient = clientDao.retrieveClientById(superClientId);
+ assertTrue(superClient.isSuper());
+ testLogin(superClientId, superClientSecret);
+ removeSuperClientFile();
+ }
+
+ private void testLogin (String superClientId, String superClientSecret)
+ throws KustvaktException {
+ Response response = requestTokenWithPassword(superClientId,
+ superClientSecret, "username", "password");
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ assertTrue(!node.at("/access_token").isMissingNode());
+ assertTrue(!node.at("/refresh_token").isMissingNode());
+ assertTrue(!node.at("/expires_in").isMissingNode());
+ assertEquals(node.at("/scope").asText(), "all");
+ assertEquals(node.at("/token_type").asText(), "Bearer");
+ }
+
+ private void removeSuperClientFile () {
+ File f = new File(path);
+ if (f.exists()) {
+ f.delete();
+ }
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/JettyServerTest.java b/src/test/java/de/ids_mannheim/korap/web/JettyServerTest.java
new file mode 100644
index 0000000..4730f2c
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/JettyServerTest.java
@@ -0,0 +1,49 @@
+package de.ids_mannheim.korap.web;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.ServerSocket;
+import java.net.URL;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ShutdownHandler;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author margaretha
+ */
+public class JettyServerTest {
+
+ static int selectedPort = 0;
+
+ @BeforeAll
+ static void testServerStarts () throws Exception {
+
+ for (int port = 1000; port <= 2000; port++) {
+ try (ServerSocket ignored = new ServerSocket(port)) {
+ selectedPort = port;
+ break;
+ }
+ catch (IOException ignored) {
+ // Port is already in use, try the next one
+ }
+ }
+
+ Server server = new Server(selectedPort);
+ ShutdownHandler shutdownHandler = new ShutdownHandler("secret");
+ server.setHandler(shutdownHandler);
+ server.start();
+ }
+
+ @Test
+ public void testShutdown () throws IOException {
+ URL url = new URL(
+ "http://localhost:" + selectedPort + "/shutdown?token=secret");
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("POST");
+ assertEquals(200, connection.getResponseCode());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/SearchKrillTest.java b/src/test/java/de/ids_mannheim/korap/web/SearchKrillTest.java
new file mode 100644
index 0000000..2034592
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/SearchKrillTest.java
@@ -0,0 +1,83 @@
+package de.ids_mannheim.korap.web;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import jakarta.annotation.PostConstruct;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.KrillIndex;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.query.serialize.MetaQueryBuilder;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * Created by hanl on 02.06.16.
+ * <p>
+ * Updated by margaretha
+ */
+public class SearchKrillTest extends SpringJerseyTest {
+
+ @Autowired
+ KustvaktConfiguration config;
+
+ SearchKrill krill = null;
+
+ @PostConstruct
+ private void createKrill () {
+ krill = new SearchKrill(config.getIndexDir());
+ assertNotNull(krill);
+ }
+
+ @Test
+ public void testIndex () throws KustvaktException {
+ KrillIndex index = krill.getIndex();
+ assertNotNull(index);
+ }
+
+ @Test
+ public void testDocSize () {
+ assertNotEquals(0, krill.getIndex().numberOf("documents"));
+ }
+
+ @Test
+ public void testMatchInfo () throws KustvaktException {
+ String matchinfo = krill.getMatch("WPD/AAA.00002/p169-197",
+ config.getFreeLicensePattern());
+ JsonNode node = JsonUtils.readTree(matchinfo);
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Invalid match identifier");
+ }
+
+ @Test
+ public void testSearch () throws KustvaktException {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[orth=der]", "poliqarp");
+ String result = krill.search(s.toJSON());
+ JsonNode node = JsonUtils.readTree(result);
+ assertNotNull(node);
+ assertNotEquals(0, node.at("/matches").size());
+ }
+
+ @Test
+ public void testTimeOut () throws KustvaktException {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[orth=der]", "poliqarp");
+ // s.setQuery("node ->malt/d[func=/.*/] node", "annis");
+ MetaQueryBuilder meta = new MetaQueryBuilder();
+ meta.addEntry("timeout", 1);
+ s.setMeta(meta);
+ String query = s.toJSON();
+ JsonNode node = JsonUtils.readTree(query);
+ assertEquals(1, node.at("/meta/timeout").asInt());
+ String result = krill.search(query);
+ node = JsonUtils.readTree(result);
+ assertEquals(true, node.at("/meta/timeExceeded").asBoolean());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/AdminLoadVCTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/AdminLoadVCTest.java
new file mode 100644
index 0000000..2c359a0
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/AdminLoadVCTest.java
@@ -0,0 +1,39 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import com.google.common.net.HttpHeaders;
+
+import de.ids_mannheim.korap.cache.VirtualCorpusCache;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+public class AdminLoadVCTest extends SpringJerseyTest {
+
+ @Test
+ public void testLoadCacheVC ()
+ throws KustvaktException, InterruptedException {
+ assertFalse(VirtualCorpusCache.contains("named-vc1"));
+ Form f = new Form();
+ f.param("token", "secret");
+ Response response = target().path(API_VERSION).path("admin").path("vc")
+ .path("load-cache").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(f));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ Thread.sleep(100);
+ assertTrue(VirtualCorpusCache.contains("named-vc1"));
+ VirtualCorpusCache.reset();
+ assertFalse(VirtualCorpusCache.contains("named-vc1"));
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/AnnotationControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/AnnotationControllerTest.java
new file mode 100644
index 0000000..e6737bb
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/AnnotationControllerTest.java
@@ -0,0 +1,82 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Response;
+
+public class AnnotationControllerTest extends SpringJerseyTest {
+
+ @Test
+ public void testAnnotationLayers () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("annotation")
+ .path("layers").request().get();
+ String entity = response.readEntity(String.class);
+ JsonNode n = JsonUtils.readTree(entity);
+ assertEquals(31, n.size());
+ n = n.get(0);
+ assertEquals(1, n.get("id").asInt());
+ // assertEquals("opennlp/p", n.get("code").asText());
+ // assertEquals("p", n.get("layer").asText());
+ // assertEquals("opennlp", n.get("foundry").asText());
+ // assertNotNull(n.get("description"));
+ }
+
+ @Test
+ public void testAnnotationFoundry () throws KustvaktException {
+ String json = "{\"codes\":[\"opennlp/*\"], \"language\":\"en\"}";
+ Response response = target().path(API_VERSION).path("annotation")
+ .path("description").request().post(Entity.json(json));
+ String entity = response.readEntity(String.class);
+ JsonNode n = JsonUtils.readTree(entity);
+ n = n.get(0);
+ assertEquals(n.get("code").asText(), "opennlp");
+ assertEquals(n.get("description").asText(), "OpenNLP");
+ assertEquals(1, n.get("layers").size());
+ n = n.get("layers").get(0);
+ assertEquals(n.get("code").asText(), "p");
+ assertEquals(n.get("description").asText(), "Part-of-Speech");
+ assertEquals(52, n.get("keys").size());
+ n = n.get("keys").get(0);
+ assertEquals(n.get("code").asText(), "ADJA");
+ assertEquals(n.get("description").asText(), "Attributive Adjective");
+ assertTrue(n.get("values") == null);
+ }
+
+ @Test
+ public void testAnnotationValues () throws KustvaktException {
+ String json = "{\"codes\":[\"mate/m\"], \"language\":\"en\"}";
+ Response response = target().path(API_VERSION).path("annotation")
+ .path("description").request().post(Entity.json(json));
+ String entity = response.readEntity(String.class);
+ JsonNode n = JsonUtils.readTree(entity);
+ n = n.get(0);
+ assertEquals(n.get("code").asText(), "mate");
+ assertEquals(n.get("description").asText(), "Mate");
+ assertEquals(1, n.get("layers").size());
+ n = n.get("layers").get(0);
+ assertEquals(n.get("code").asText(), "m");
+ assertEquals(n.get("description").asText(), "Morphology");
+ assertEquals(8, n.get("keys").size());
+ n = n.get("keys").get(1);
+ assertEquals(n.get("code").asText(), "case");
+ assertEquals(n.get("description").asText(), "Case");
+ assertEquals(5, n.get("values").size());
+ n = n.get("values");
+ Iterator<Entry<String, JsonNode>> fields = n.fields();
+ Entry<String, JsonNode> e = fields.next();
+ assertEquals(e.getKey(), "*");
+ assertEquals(e.getValue().asText(), "Undefined");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/AvailabilityTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/AvailabilityTest.java
new file mode 100644
index 0000000..42d7a68
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/AvailabilityTest.java
@@ -0,0 +1,379 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class AvailabilityTest extends SpringJerseyTest {
+
+ private void checkAndFree (String json) throws KustvaktException {
+ JsonNode node = JsonUtils.readTree(json);
+ assertEquals(node.at("/collection/operands/0/key").asText(),
+ "availability");
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "CC-BY.*");
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(FREE)");
+ }
+
+ private void checkAndPublic (String json) throws KustvaktException {
+ JsonNode node = JsonUtils.readTree(json);
+ assertNotNull(node);
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(
+ node.at("/collection/operands/0/operands/0/match").asText(),
+ "match:eq");
+ assertEquals(node.at("/collection/operands/0/operands/0/type").asText(),
+ "type:regex");
+ assertEquals(node.at("/collection/operands/0/operands/0/key").asText(),
+ "availability");
+ assertEquals(
+ node.at("/collection/operands/0/operands/0/value").asText(),
+ "CC-BY.*");
+ assertEquals(
+ node.at("/collection/operands/0/operands/1/operands/0/match")
+ .asText(),
+ "match:eq");
+ assertEquals(
+ node.at("/collection/operands/0/operands/1/operands/0/value")
+ .asText(),
+ "ACA.*");
+ assertEquals(
+ node.at("/collection/operands/0/operands/1/operands/1/match")
+ .asText(),
+ "match:eq");
+ assertEquals(
+ node.at("/collection/operands/0/operands/1/operands/1/value")
+ .asText(),
+ "QAO-NC");
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(PUB)");
+ }
+
+ private void checkAndPublicWithACA (String json) throws KustvaktException {
+ JsonNode node = JsonUtils.readTree(json);
+ assertNotNull(node);
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(PUB)");
+ assertEquals(node.at("/collection/operands/1/match").asText(),
+ "match:eq");
+ assertEquals(node.at("/collection/operands/1/type").asText(),
+ "type:regex");
+ assertEquals(node.at("/collection/operands/1/key").asText(),
+ "availability");
+ assertEquals(node.at("/collection/operands/1/value").asText(), "ACA.*");
+ node = node.at("/collection/operands/0");
+ assertEquals(node.at("/operands/0/match").asText(), "match:eq");
+ assertEquals(node.at("/operands/0/type").asText(), "type:regex");
+ assertEquals(node.at("/operands/0/key").asText(), "availability");
+ assertEquals(node.at("/operands/0/value").asText(), "CC-BY.*");
+ assertEquals(node.at("/operands/1/operands/0/match").asText(),
+ "match:eq");
+ assertEquals(node.at("/operands/1/operands/0/type").asText(),
+ "type:regex");
+ assertEquals(node.at("/operands/1/operands/0/key").asText(),
+ "availability");
+ assertEquals(node.at("/operands/1/operands/0/value").asText(), "ACA.*");
+ }
+
+ private void checkAndAllWithACA (String json) throws KustvaktException {
+ JsonNode node = JsonUtils.readTree(json);
+ assertNotNull(node);
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(ALL)");
+ assertEquals(node.at("/collection/operands/1/match").asText(),
+ "match:eq");
+ assertEquals(node.at("/collection/operands/1/type").asText(),
+ "type:regex");
+ assertEquals(node.at("/collection/operands/1/key").asText(),
+ "availability");
+ assertEquals(node.at("/collection/operands/1/value").asText(), "ACA.*");
+ node = node.at("/collection/operands/0");
+ assertEquals(node.at("/operands/0/match").asText(), "match:eq");
+ assertEquals(node.at("/operands/0/type").asText(), "type:regex");
+ assertEquals(node.at("/operands/0/key").asText(), "availability");
+ assertEquals(node.at("/operands/0/value").asText(), "CC-BY.*");
+ assertEquals(
+ node.at("/operands/1/operands/1/operands/0/match").asText(),
+ "match:eq");
+ assertEquals(
+ node.at("/operands/1/operands/1/operands/0/value").asText(),
+ "QAO-NC");
+ assertEquals(
+ node.at("/operands/1/operands/1/operands/1/match").asText(),
+ "match:eq");
+ assertEquals(
+ node.at("/operands/1/operands/1/operands/1/value").asText(),
+ "QAO.*");
+ }
+
+ private Response searchQuery (String collectionQuery) {
+ return target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("cq", collectionQuery).request().get();
+ }
+
+ private Response searchQueryWithIP (String collectionQuery, String ip)
+ throws ProcessingException, KustvaktException {
+ return target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("cq", collectionQuery).request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .header(HttpHeaders.X_FORWARDED_FOR, ip).get();
+ }
+
+ @Test
+ public void testAvailabilityFreeAuthorized () throws KustvaktException {
+ Response response = searchQuery("availability = CC-BY-SA");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testAvailabilityRegexFreeAuthorized ()
+ throws KustvaktException {
+ Response response = searchQuery("availability = /.*BY.*/");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testAvailabilityFreeUnauthorized () throws KustvaktException {
+ Response response = searchQuery("availability = ACA-NC");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testAvailabilityRegexFreeUnauthorized ()
+ throws KustvaktException {
+ Response response = searchQuery("availability = /ACA.*/");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testAvailabilityRegexNoRewrite () throws KustvaktException {
+ Response response = searchQuery(
+ "availability = /CC-BY.*/ & availability = /ACA.*/");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String json = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(json);
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(node.at("/collection/operands/0/match").asText(),
+ "match:eq");
+ assertEquals(node.at("/collection/operands/0/type").asText(),
+ "type:regex");
+ assertEquals(node.at("/collection/operands/0/key").asText(),
+ "availability");
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "CC-BY.*");
+ assertEquals(node.at("/collection/operands/1/match").asText(),
+ "match:eq");
+ assertEquals(node.at("/collection/operands/1/value").asText(), "ACA.*");
+ }
+
+ @Test
+ public void testAvailabilityRegexFreeUnauthorized3 ()
+ throws KustvaktException {
+ Response response = searchQuery("availability = /.*NC.*/");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // System.out.println(response.readEntity(String.class));
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testNegationAvailabilityFreeUnauthorized ()
+ throws KustvaktException {
+ Response response = searchQuery("availability != /CC-BY.*/");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testNegationAvailabilityFreeUnauthorized2 ()
+ throws KustvaktException {
+ Response response = searchQuery("availability != /.*BY.*/");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testNegationAvailabilityWithOperationOrUnauthorized ()
+ throws KustvaktException {
+ Response response = searchQuery(
+ "availability = /CC-BY.*/ | availability != /CC-BY.*/");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testComplexNegationAvailabilityFreeUnauthorized ()
+ throws KustvaktException {
+ Response response = searchQuery(
+ "textClass=politik & availability != /CC-BY.*/");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testComplexAvailabilityFreeUnauthorized ()
+ throws KustvaktException {
+ Response response = searchQuery(
+ "textClass=politik & availability=ACA-NC");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testComplexAvailabilityFreeUnauthorized3 ()
+ throws KustvaktException {
+ Response response = searchQuery(
+ "textClass=politik & availability=/.*NC.*/");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testAvailabilityPublicAuthorized () throws KustvaktException {
+ Response response = searchQueryWithIP("availability=ACA-NC",
+ "149.27.0.32");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndPublic(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testAvailabilityPublicUnauthorized () throws KustvaktException {
+ Response response = searchQueryWithIP("availability=QAO-NC-LOC:ids",
+ "149.27.0.32");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndPublic(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testAvailabilityRegexPublicAuthorized ()
+ throws KustvaktException {
+ Response response = searchQueryWithIP("availability= /ACA.*/",
+ "149.27.0.32");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndPublicWithACA(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testNegationAvailabilityPublicUnauthorized ()
+ throws KustvaktException {
+ Response response = searchQueryWithIP("availability != ACA-NC",
+ "149.27.0.32");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndPublic(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testNegationAvailabilityRegexPublicUnauthorized ()
+ throws KustvaktException {
+ Response response = searchQueryWithIP("availability != /ACA.*/",
+ "149.27.0.32");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndPublic(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testComplexAvailabilityPublicUnauthorized ()
+ throws KustvaktException {
+ Response response = searchQueryWithIP(
+ "textClass=politik & availability=QAO-NC-LOC:ids",
+ "149.27.0.32");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndPublic(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testNegationComplexAvailabilityPublicUnauthorized ()
+ throws KustvaktException {
+ Response response = searchQueryWithIP(
+ "textClass=politik & availability!=QAO-NC-LOC:ids",
+ "149.27.0.32");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndPublic(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testAvailabilityRegexAllAuthorized () throws KustvaktException {
+ Response response = searchQueryWithIP("availability= /ACA.*/",
+ "10.27.0.32");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndAllWithACA(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testAvailabilityOr () throws KustvaktException {
+ Response response = searchQuery(
+ "availability=/CC-BY.*/ | availability=/ACA.*/");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testRedundancyOrPub () throws KustvaktException {
+ Response response = searchQueryWithIP(
+ "availability=/CC-BY.*/ | availability=/ACA.*/ | availability=/QAO-NC/",
+ "149.27.0.32");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String json = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(json);
+ assertTrue(node.at("/collection/rewrites").isMissingNode());
+ assertEquals(node.at("/collection/operation").asText(), "operation:or");
+ }
+
+ @Test
+ public void testAvailabilityOrCorpusSigle () throws KustvaktException {
+ Response response = searchQuery(
+ "availability=/CC-BY.*/ | corpusSigle=GOE");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testOrWithoutAvailability () throws KustvaktException {
+ Response response = searchQuery("corpusSigle=GOE | textClass=politik");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+
+ @Test
+ public void testWithoutAvailability () throws KustvaktException {
+ Response response = searchQuery("corpusSigle=GOE");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ checkAndFree(response.readEntity(String.class));
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/FreeResourceControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/FreeResourceControllerTest.java
new file mode 100644
index 0000000..cf74436
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/FreeResourceControllerTest.java
@@ -0,0 +1,31 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.test.context.ContextConfiguration;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.ws.rs.core.Response;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+@ContextConfiguration("classpath:test-resource-config.xml")
+public class FreeResourceControllerTest extends SpringJerseyTest {
+
+ @Test
+ public void testResource () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("resource")
+ .request().get();
+ String entity = response.readEntity(String.class);
+ JsonNode n = JsonUtils.readTree(entity).get(0);
+ assertEquals(n.at("/resourceId").asText(), "WPD17");
+ assertEquals(n.at("/titles/de").asText(),
+ "Deutsche Wikipedia Artikel 2017");
+ assertEquals(n.at("/titles/en").asText(),
+ "German Wikipedia Articles 2017");
+ assertEquals(1, n.at("/languages").size());
+ assertEquals(6, n.at("/layers").size());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/IndexControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/IndexControllerTest.java
new file mode 100644
index 0000000..3b9dfbc
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/IndexControllerTest.java
@@ -0,0 +1,76 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MediaType;
+
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.ws.rs.core.Response;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.SearchKrill;
+
+/**
+ * @author margaretha
+ */
+public class IndexControllerTest extends SpringJerseyTest {
+
+ @Autowired
+ private SearchKrill searchKrill;
+
+ @Test
+ public void testCloseIndex () throws IOException, KustvaktException,
+ URISyntaxException, InterruptedException {
+ URI uri = IndexControllerTest.class.getClassLoader()
+ .getResource("vc/named-vc1.jsonld").toURI();
+ Path toLink = Paths.get(uri);
+ Path symLink = Paths.get("vc/named-vc1.jsonld");
+ Path vcPath = Paths.get("vc");
+ if (!Files.exists(vcPath)) {
+ Files.createDirectory(vcPath);
+ }
+ if (Files.exists(symLink, LinkOption.NOFOLLOW_LINKS)) {
+ Files.delete(symLink);
+ }
+ Files.createSymbolicLink(symLink, toLink);
+ searchKrill.getStatistics(null);
+ assertEquals(true, searchKrill.getIndex().isReaderOpen());
+ Form form = new Form();
+ form.param("token", "secret");
+ Response response = target().path(API_VERSION).path("index")
+ .path("close").request().post(Entity.form(form));
+ assertEquals(HttpStatus.SC_OK, response.getStatus());
+ assertEquals(false, searchKrill.getIndex().isReaderOpen());
+ // Cleaning database and cache
+ Thread.sleep(200);
+ response = target().path(API_VERSION).path("vc").path("~system")
+ .path("named-vc1").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "pass"))
+ .delete();
+ response = target().path(API_VERSION).path("vc").path("~system")
+ .path("named-vc1").request().get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/InfoControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/InfoControllerTest.java
new file mode 100644
index 0000000..ab60ba3
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/InfoControllerTest.java
@@ -0,0 +1,45 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.ServiceInfo;
+import de.ids_mannheim.korap.web.SearchKrill;
+
+public class InfoControllerTest extends SpringJerseyTest {
+
+ @Autowired
+ private KustvaktConfiguration config;
+
+ @Autowired
+ private SearchKrill krill;
+
+ @Test
+ public void testInfo () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("info").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(config.getCurrentVersion(),
+ node.at("/latest_api_version").asText());
+ assertEquals(config.getSupportedVersions().size(),
+ node.at("/supported_api_versions").size());
+ assertEquals(ServiceInfo.getInfo().getVersion(),
+ node.at("/kustvakt_version").asText());
+ assertEquals(krill.getIndex().getVersion(),
+ node.at("/krill_version").asText());
+ QuerySerializer s = new QuerySerializer();
+ assertEquals(s.getVersion(), node.at("/koral_version").asText());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/MatchInfoControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/MatchInfoControllerTest.java
new file mode 100644
index 0000000..ec282c9
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/MatchInfoControllerTest.java
@@ -0,0 +1,113 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class MatchInfoControllerTest extends SpringJerseyTest {
+
+ @Test
+ public void testGetMatchInfoPublicCorpus () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGA").path("01784").path("p36-100")
+ .path("matchInfo").queryParam("foundry", "*").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node);
+ assertEquals(node.at("/textSigle").asText(), "GOE/AGA/01784");
+ assertEquals(node.at("/title").asText(), "Belagerung von Mainz");
+ assertEquals(node.at("/author").asText(),
+ "Goethe, Johann Wolfgang von");
+ assertTrue(node.at("/snippet").asText()
+ .startsWith("<span class=\"context-left\"></span>"
+ + "<span class=\"match\">"));
+ }
+
+ @Test
+ public void testGetMatchInfoNotAllowed () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGI").path("04846").path("p36875-36876")
+ .path("matchInfo").queryParam("foundry", "*").request().get();
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(
+ "Retrieving resource with ID "
+ + "match-GOE/AGI/04846-p36875-36876 is not allowed.",
+ node.at("/errors/0/1").asText());
+ assertTrue(node.at("/snippet").isMissingNode());
+ }
+
+ @Test
+ public void testGetMatchInfoWithAuthentication () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGI").path("04846").path("p36875-36876")
+ .path("matchInfo").queryParam("foundry", "*").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "172.27.0.32").get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node);
+ assertEquals(node.at("/textSigle").asText(), "GOE/AGI/04846");
+ assertEquals(node.at("/title").asText(),
+ "Zweiter römischer Aufenthalt");
+ assertEquals(node.at("/subTitle").asText(),
+ "vom Juni 1787 bis April 1788");
+ assertEquals(node.at("/author").asText(),
+ "Goethe, Johann Wolfgang von");
+ assertTrue(node.at("/snippet").asText()
+ .startsWith("<span class=\"context-left\"></span>"
+ + "<span class=\"match\">"));
+ assertEquals(node.at("/availability").asText(), "QAO-NC-LOC:ids");
+ }
+
+ @Test
+ public void testAvailabilityAll () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGD").path("00000").path("p75-76").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "10.27.0.32").get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testAvailabilityAllUnauthorized () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGD").path("00000").path("p75-76").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "170.27.0.32").get();
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(
+ "Retrieving resource with ID "
+ + "match-GOE/AGD/00000-p75-76 is not allowed.",
+ node.at("/errors/0/1").asText());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/MetadataControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/MetadataControllerTest.java
new file mode 100644
index 0000000..dcc63d7
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/MetadataControllerTest.java
@@ -0,0 +1,117 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class MetadataControllerTest extends SpringJerseyTest {
+
+ @Test
+ public void testRetrieveMetadataWithField () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGA").path("01784")
+ .queryParam("fields", "author").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/document/fields/0/key").asText(), "author");
+ }
+
+ @Test
+ public void testRetrieveMetadataWithMultipleFields ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGA").path("01784")
+ .queryParam("fields", "author,title").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/document/fields/0/key").asText(), "author");
+ assertEquals(node.at("/document/fields/1/key").asText(), "title");
+ }
+
+ @Test
+ public void testFreeMetadata () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGA").path("01784").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertTrue(!node.at("/document").isMissingNode());
+ }
+
+ // EM: currently all metadata are allowed
+ @Test
+ @Disabled
+ public void testMetadataUnauthorized () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGI").path("04846").request().get();
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(
+ "Retrieving resource with ID "
+ + "GOE/AGI/04846 is not allowed.",
+ node.at("/errors/0/1").asText());
+ }
+
+ @Test
+ public void testMetadataWithAuthentication () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGI").path("04846").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "172.27.0.32").get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testMetadataAvailabilityAll () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGI").path("00000").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "10.27.0.32").get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ // EM: currently all metadata are allowed
+ @Test
+ @Disabled
+ public void testMetadataAvailabilityAllUnauthorized ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").path("AGD").path("00000").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "170.27.0.32").get();
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(
+ "Retrieving resource with ID "
+ + "GOE/AGD/00000 is not allowed.",
+ node.at("/errors/0/1").asText());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/MultipleCorpusQueryTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/MultipleCorpusQueryTest.java
new file mode 100644
index 0000000..775afd8
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/MultipleCorpusQueryTest.java
@@ -0,0 +1,75 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class MultipleCorpusQueryTest extends SpringJerseyTest {
+
+ @Test
+ public void testSearchGet () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "das").queryParam("ql", "poliqarp")
+ .queryParam("cq", "pubPlace=München")
+ .queryParam("cq", "textSigle=\"GOE/AGA/01784\"").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ node = node.at("/collection/operands/1");
+ assertEquals(node.at("/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/operation").asText(), "operation:and");
+ assertEquals(2, node.at("/operands").size());
+ assertEquals(node.at("/operands/0/@type").asText(), "koral:doc");
+ assertEquals(node.at("/operands/0/match").asText(), "match:eq");
+ assertEquals(node.at("/operands/0/key").asText(), "pubPlace");
+ assertEquals(node.at("/operands/0/value").asText(), "München");
+ assertEquals(node.at("/operands/1/key").asText(), "textSigle");
+ assertEquals(node.at("/operands/1/value").asText(), "GOE/AGA/01784");
+ }
+
+ @Test
+ public void testStatisticsWithMultipleCq ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("cq", "textType=Abhandlung")
+ .queryParam("cq", "corpusSigle=GOE").request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/documents").asInt());
+ assertEquals(138180, node.at("/tokens").asInt());
+ assertEquals(5687, node.at("/sentences").asInt());
+ assertEquals(258, node.at("/paragraphs").asInt());
+ assertTrue(node.at("/warnings").isMissingNode());
+ }
+
+ @Test
+ public void testStatisticsWithMultipleCorpusQuery ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("corpusQuery", "textType=Autobiographie")
+ .queryParam("corpusQuery", "corpusSigle=GOE").request()
+ .method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(9, node.at("/documents").asInt());
+ assertEquals(527662, node.at("/tokens").asInt());
+ assertEquals(19387, node.at("/sentences").asInt());
+ assertEquals(514, node.at("/paragraphs").asInt());
+ assertEquals(StatusCodes.DEPRECATED, node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/1").asText(),
+ "Parameter corpusQuery is deprecated in favor of cq.");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
new file mode 100644
index 0000000..6b5627d
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
@@ -0,0 +1,231 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.http.entity.ContentType;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import com.nimbusds.oauth2.sdk.GrantType;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.constant.OAuth2Scope;
+import de.ids_mannheim.korap.constant.TokenType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class OAuth2AccessTokenTest extends OAuth2TestBase {
+
+ private String userAuthHeader;
+
+ private String clientAuthHeader;
+
+ public OAuth2AccessTokenTest () throws KustvaktException {
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ clientAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(confidentialClientId,
+ clientSecret);
+ }
+
+ @Test
+ public void testScopeWithSuperClient () throws KustvaktException {
+ Response response = requestTokenWithDoryPassword(superClientId,
+ clientSecret);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(node.at("/scope").asText(), "all");
+ String accessToken = node.at("/access_token").asText();
+ // test list user group
+ response = target().path(API_VERSION).path("group").request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(2, node.size());
+ }
+
+ @Test
+ public void testCustomScope () throws KustvaktException {
+ Response response = requestAuthorizationCode("code",
+ confidentialClientId, "", OAuth2Scope.VC_INFO.toString(), "",
+ userAuthHeader);
+ String code = parseAuthorizationCode(response);
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String token = node.at("/access_token").asText();
+ assertTrue(node.at("/scope").asText()
+ .contains(OAuth2Scope.VC_INFO.toString()));
+ // test list vc using the token
+ response = target().path(API_VERSION).path("vc").request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + token).get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(4, node.size());
+ }
+
+ @Test
+ public void testDefaultScope () throws KustvaktException, IOException {
+ String code = requestAuthorizationCode(confidentialClientId,
+ userAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String accessToken = node.at("/access_token").asText();
+ testScopeNotAuthorized(accessToken);
+ testScopeNotAuthorize2(accessToken);
+ testSearchWithOAuth2Token(accessToken);
+ }
+
+ private void testScopeNotAuthorized (String accessToken)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .get();
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Scope vc_info is not authorized");
+ }
+
+ private void testScopeNotAuthorize2 (String accessToken)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("access")
+ .request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Scope vc_access_info is not authorized");
+ }
+
+ @Test
+ public void testSearchWithUnknownToken ()
+ throws KustvaktException, IOException {
+ Response response = searchWithAccessToken(
+ "ljsa8tKNRSczJhk20öhq92zG8z350");
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(StatusCodes.INVALID_ACCESS_TOKEN,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Access token is invalid");
+ }
+
+ @Test
+ public void testRevokeAccessTokenConfidentialClient ()
+ throws KustvaktException {
+ String code = requestAuthorizationCode(confidentialClientId,
+ userAuthHeader);
+ JsonNode node = requestTokenWithAuthorizationCodeAndHeader(
+ confidentialClientId, code, clientAuthHeader);
+ String accessToken = node.at("/access_token").asText();
+ Form form = new Form();
+ form.param("token", accessToken);
+ form.param("client_id", confidentialClientId);
+ form.param("client_secret", "secret");
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("revoke").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ testSearchWithRevokedAccessToken(accessToken);
+ }
+
+ @Test
+ public void testRevokeAccessTokenPublicClientViaSuperClient ()
+ throws KustvaktException {
+ String code = requestAuthorizationCode(publicClientId, userAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ publicClientId, "", code);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String accessToken = node.at("/access_token").asText();
+ testRevokeTokenViaSuperClient(accessToken, userAuthHeader);
+ testSearchWithRevokedAccessToken(accessToken);
+ }
+
+ @Test
+ public void testAccessTokenAfterRequestRefreshToken ()
+ throws KustvaktException, IOException {
+ String code = requestAuthorizationCode(confidentialClientId,
+ userAuthHeader);
+ JsonNode node = requestTokenWithAuthorizationCodeAndHeader(
+ confidentialClientId, code, clientAuthHeader);
+ String accessToken = node.at("/access_token").asText();
+ String refreshToken = node.at("/refresh_token").asText();
+ Form form = new Form();
+ form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+ form.param("client_id", confidentialClientId);
+ form.param("client_secret", "secret");
+ form.param("refresh_token", refreshToken);
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(entity);
+ assertNotNull(node.at("/access_token").asText());
+ assertTrue(!refreshToken.equals(node.at("/refresh_token").asText()));
+ testSearchWithRevokedAccessToken(accessToken);
+ }
+
+ @Test
+ public void testRequestAuthorizationWithBearerTokenUnauthorized ()
+ throws KustvaktException {
+ String code = requestAuthorizationCode(confidentialClientId,
+ userAuthHeader);
+ JsonNode node = requestTokenWithAuthorizationCodeAndHeader(
+ confidentialClientId, code, clientAuthHeader);
+ String userAuthToken = node.at("/access_token").asText();
+ Response response = requestAuthorizationCode("code",
+ confidentialClientId, "", "search", "",
+ "Bearer " + userAuthToken);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Scope authorize is not authorized");
+ }
+
+ @Test
+ public void testRequestAuthorizationWithBearerToken ()
+ throws KustvaktException {
+ Response response = requestTokenWithDoryPassword(superClientId,
+ clientSecret);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ String userAuthToken = node.at("/access_token").asText();
+ assertNotNull(userAuthToken);
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ String code = requestAuthorizationCode(superClientId,
+ "Bearer " + userAuthToken);
+ assertNotNull(code);
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java
new file mode 100644
index 0000000..fabc267
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java
@@ -0,0 +1,188 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.http.entity.ContentType;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
+import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+@Order(Integer.MAX_VALUE) // make sure this runs as last test as it removes tokens
+public class OAuth2AdminControllerTest extends OAuth2TestBase {
+
+ private String username = "OAuth2AdminControllerTest";
+
+ private String adminAuthHeader;
+
+ private String userAuthHeader;
+
+ @Autowired
+ private RefreshTokenDao refreshDao;
+
+ @Autowired
+ private AccessTokenDao accessDao;
+
+ public OAuth2AdminControllerTest () throws KustvaktException {
+ adminAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "password");
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ }
+
+ private Response updateClientPrivilege (String username, Form form)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("admin")
+ .path("oauth2").path("client").path("privilege").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ return response;
+ }
+
+ private Response updateClientPrivilegeWithAdminToken (String clientId)
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("client_id", clientId);
+ form.param("super", Boolean.toString(false));
+ // adminToken
+ form.param("token", "secret");
+ Response response = target().path(API_VERSION).path("admin")
+ .path("oauth2").path("client").path("privilege").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ return response;
+ }
+
+ private void testUpdateClientPriviledgeUnauthorized (Form form)
+ throws ProcessingException, KustvaktException {
+ Response response = updateClientPrivilege(username, form);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ @Order(1)
+ public void testCleanExpiredTokensUsingAdminToken ()
+ throws KustvaktException {
+ createExpiredAccessToken();
+
+ int refreshTokensBefore = refreshDao.retrieveInvalidRefreshTokens()
+ .size();
+
+ assertTrue(refreshTokensBefore > 0);
+ int accessTokensBefore = accessDao.retrieveInvalidAccessTokens().size();
+ assertTrue(accessTokensBefore > 0);
+ Form form = new Form();
+ form.param("token", "secret");
+ target().path(API_VERSION).path("admin").path("oauth2").path("token")
+ .path("clean").request().post(Entity.form(form));
+ assertEquals(0, refreshDao.retrieveInvalidRefreshTokens().size());
+ assertEquals(0, accessDao.retrieveInvalidAccessTokens().size());
+ }
+
+ @Test
+ @Order(2)
+ public void testCleanRevokedTokens () throws KustvaktException {
+
+ int accessTokensBefore = accessDao.retrieveInvalidAccessTokens().size();
+ String code = requestAuthorizationCode(publicClientId, userAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ publicClientId, clientSecret, code);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ String accessToken = node.at("/access_token").asText();
+ testRevokeToken(accessToken, publicClientId, null, ACCESS_TOKEN_TYPE);
+ int accessTokensAfter = accessDao.retrieveInvalidAccessTokens().size();
+ assertEquals(accessTokensAfter, accessTokensBefore + 1);
+ target().path(API_VERSION).path("admin").path("oauth2").path("token")
+ .path("clean").request()
+ .header(Attributes.AUTHORIZATION, adminAuthHeader).post(null);
+ assertEquals(0, accessDao.retrieveInvalidAccessTokens().size());
+ }
+
+ @Test
+ public void testUpdateClientPrivilege () throws KustvaktException {
+ // register a client
+ Response response = registerConfidentialClient(username);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String clientId = node.at("/client_id").asText();
+ String clientSecret = node.at("/client_secret").asText();
+ // request an access token
+ String clientAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(clientId, clientSecret);
+ String code = requestAuthorizationCode(clientId, userAuthHeader);
+ node = requestTokenWithAuthorizationCodeAndHeader(clientId, code,
+ clientAuthHeader);
+ String accessToken = node.at("/access_token").asText();
+ // update client priviledge to super client
+ Form form = new Form();
+ form.param("client_id", clientId);
+ form.param("super", Boolean.toString(true));
+ testUpdateClientPriviledgeUnauthorized(form);
+ response = updateClientPrivilege("admin", form);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ testAccessTokenAfterUpgradingClient(clientId, accessToken);
+ // degrade a super client to a common client
+ updateClientPrivilegeWithAdminToken(clientId);
+ testAccessTokenAfterDegradingSuperClient(clientId, accessToken);
+ deregisterClient(username, clientId);
+ }
+
+ // old access tokens retain their scopes
+ private void testAccessTokenAfterUpgradingClient (String clientId,
+ String accessToken) throws KustvaktException {
+ JsonNode node = retrieveClientInfo(clientId, "admin");
+ assertTrue(node.at("/super").asBoolean());
+ // list vc
+ Response response = target().path(API_VERSION).path("vc").request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .get();
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Scope vc_info is not authorized");
+ // search
+ response = searchWithAccessToken(accessToken);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ private void testAccessTokenAfterDegradingSuperClient (String clientId,
+ String accessToken) throws KustvaktException {
+ JsonNode node = retrieveClientInfo(clientId, username);
+ assertTrue(node.at("/isSuper").isMissingNode());
+ Response response = searchWithAccessToken(accessToken);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ACCESS_TOKEN,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Access token is invalid");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationPostTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationPostTest.java
new file mode 100644
index 0000000..8372acc
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationPostTest.java
@@ -0,0 +1,87 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.net.URI;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.http.entity.ContentType;
+import org.glassfish.jersey.uri.UriComponent;
+import org.junit.jupiter.api.Test;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.constant.TokenType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class OAuth2AuthorizationPostTest extends OAuth2TestBase {
+
+ public String userAuthHeader;
+
+ public OAuth2AuthorizationPostTest () throws KustvaktException {
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ }
+
+ private Response requestAuthorizationCode (Form form, String authHeader)
+ throws KustvaktException {
+ return target().path(API_VERSION).path("oauth2").path("authorize")
+ .request().header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ }
+
+ @Test
+ public void testAuthorizeConfidentialClient () throws KustvaktException {
+ Form form = new Form();
+ form.param("response_type", "code");
+ form.param("client_id", confidentialClientId);
+ form.param("state", "thisIsMyState");
+ form.param("scope", "search");
+ Response response = requestAuthorizationCode(form, userAuthHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+ URI redirectUri = response.getLocation();
+ MultiValueMap<String, String> params = UriComponentsBuilder
+ .fromUri(redirectUri).build().getQueryParams();
+ assertNotNull(params.getFirst("code"));
+ assertEquals(params.getFirst("state"), "thisIsMyState");
+ }
+
+ @Test
+ public void testRequestTokenAuthorizationConfidential ()
+ throws KustvaktException {
+ Form authForm = new Form();
+ authForm.param("response_type", "code");
+ authForm.param("client_id", confidentialClientId);
+ authForm.param("scope", "search");
+ Response response = requestAuthorizationCode(authForm, userAuthHeader);
+ URI redirectUri = response.getLocation();
+
+ MultivaluedMap<String, String> params = UriComponent
+ .decodeQuery(redirectUri, true);
+ String code = params.get("code").get(0);
+
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node.at("/access_token").asText());
+ assertNotNull(node.at("/refresh_token").asText());
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationTest.java
new file mode 100644
index 0000000..27903e7
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationTest.java
@@ -0,0 +1,293 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URI;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+public class OAuth2AuthorizationTest extends OAuth2TestBase {
+
+ private String userAuthHeader;
+
+ public OAuth2AuthorizationTest () throws KustvaktException {
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ }
+
+ @Test
+ public void testAuthorizeUnauthenticated () throws KustvaktException {
+
+ Response response = requestAuthorizationCode("code", publicClientId, "",
+ "search match_info", "", "");
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Unauthorized operation for user: guest",
+ node.at("/errors/0/1").asText());
+ }
+
+ @Test
+ public void testAuthorizeConfidentialClient () throws KustvaktException {
+ // with registered redirect URI
+ Response response = requestAuthorizationCode("code",
+ confidentialClientId, "", "match_info search client_info",
+ state, userAuthHeader);
+
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+ URI redirectUri = response.getLocation();
+ MultivaluedMap<String, String> params = getQueryParamsFromURI(
+ redirectUri);
+ assertNotNull(params.getFirst("code"));
+ assertEquals(state, params.getFirst("state"));
+ }
+
+ @Test
+ public void testAuthorizePublicClient () throws KustvaktException {
+ // with registered redirect URI
+ String code = requestAuthorizationCode(publicClientId, userAuthHeader);
+ assertNotNull(code);
+ }
+
+ @Test
+ public void testAuthorizeWithRedirectUri () throws KustvaktException {
+ Response response = requestAuthorizationCode("code", publicClientId2,
+ "https://public.com/redirect", "search match_info", "",
+ userAuthHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+
+ URI redirectUri = response.getLocation();
+ assertEquals("https", redirectUri.getScheme());
+ assertEquals("public.com", redirectUri.getHost());
+ assertEquals("/redirect", redirectUri.getPath());
+
+ assertTrue(redirectUri.getQuery().startsWith("code="));
+ }
+
+ @Test
+ public void testAuthorizeWithoutScope () throws KustvaktException {
+ Response response = requestAuthorizationCode("code",
+ confidentialClientId, "", "", "", userAuthHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+
+ URI redirectUri = response.getLocation();
+ assertEquals(redirectUri.getScheme(), "https");
+ assertEquals(redirectUri.getHost(), "third.party.com");
+ assertEquals(redirectUri.getPath(), "/confidential/redirect");
+
+ String[] queryParts = redirectUri.getQuery().split("&");
+ assertEquals("error_description=scope+is+required", queryParts[1]);
+ assertEquals("error=invalid_scope", queryParts[0]);
+ }
+
+ @Test
+ public void testAuthorizeMissingClientId () throws KustvaktException {
+ Response response = requestAuthorizationCode("code", "", "", "search",
+ "", userAuthHeader);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals("Missing parameter: client_id",
+ node.at("/error_description").asText());
+ }
+
+ @Test
+ public void testAuthorizeMissingRedirectUri () throws KustvaktException {
+ Response response = requestAuthorizationCode("code", publicClientId2,
+ "", "search", state, userAuthHeader);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertEquals("Missing parameter: redirect URI",
+ node.at("/error_description").asText());
+ assertEquals(state, node.at("/state").asText());
+ }
+
+ @Test
+ public void testAuthorizeMissingResponseType () throws KustvaktException {
+ Response response = requestAuthorizationCode("", confidentialClientId,
+ "", "search", "", userAuthHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+
+ assertEquals(
+ "https://third.party.com/confidential/redirect?"
+ + "error=invalid_request_uri&"
+ + "error_description=Missing+response_type+parameter",
+ response.getLocation().toString());
+ }
+
+ @Test
+ public void testAuthorizeMissingResponseTypeWithoutClientId ()
+ throws KustvaktException {
+ Response response = requestAuthorizationCode("", "", "", "search", "",
+ userAuthHeader);
+
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertEquals("Missing parameter: client_id",
+ node.at("/error_description").asText());
+ }
+
+ @Test
+ public void testAuthorizeInvalidClientId () throws KustvaktException {
+ Response response = requestAuthorizationCode("code",
+ "unknown-client-id", "", "search", "", userAuthHeader);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_CLIENT, node.at("/error").asText());
+ assertEquals("Unknown client: unknown-client-id",
+ node.at("/error_description").asText());
+ }
+
+ @Test
+ public void testAuthorizeDifferentRedirectUri () throws KustvaktException {
+ String redirectUri = "https://different.uri/redirect";
+ Response response = requestAuthorizationCode("code",
+ confidentialClientId, redirectUri, "", state, userAuthHeader);
+
+ testInvalidRedirectUri(response.readEntity(String.class),
+ response.getHeaderString("Content-Type"), true,
+ response.getStatus());
+ }
+
+ @Test
+ public void testAuthorizeWithRedirectUriLocalhost ()
+ throws KustvaktException {
+ Response response = requestAuthorizationCode("code", publicClientId2,
+ "http://localhost:1410", "search", state, userAuthHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+
+ URI redirectUri = response.getLocation();
+ MultivaluedMap<String, String> params = getQueryParamsFromURI(
+ redirectUri);
+ assertNotNull(params.getFirst("code"));
+ assertEquals(state, params.getFirst("state"));
+ }
+
+ @Test
+ public void testAuthorizeWithRedirectUriFragment ()
+ throws KustvaktException {
+ Response response = requestAuthorizationCode("code", publicClientId2,
+ "http://public.com/index.html#redirect", "search", state,
+ userAuthHeader);
+ testInvalidRedirectUri(response.readEntity(String.class),
+ response.getHeaderString("Content-Type"), true,
+ response.getStatus());
+ }
+
+ @Test
+ public void testAuthorizeInvalidRedirectUri () throws KustvaktException {
+ // host not allowed by Apache URI Validator
+ String redirectUri = "https://public.uri/redirect";
+ Response response = requestAuthorizationCode("code", publicClientId2,
+ redirectUri, "", state, userAuthHeader);
+ testInvalidRedirectUri(response.readEntity(String.class),
+ response.getHeaderString("Content-Type"), true,
+ response.getStatus());
+ }
+
+ @Test
+ public void testAuthorizeInvalidResponseType () throws KustvaktException {
+ // without redirect URI in the request
+ Response response = requestAuthorizationCode("string",
+ confidentialClientId, "", "search", state, userAuthHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+
+ assertEquals("https://third.party.com/confidential/redirect?"
+ + "error=unsupported_response_type"
+ + "&error_description=Unsupported+response+type.+Only+code+is+supported."
+ + "&state=thisIsMyState", response.getLocation().toString());
+
+ // with redirect URI, and no registered redirect URI
+ response = requestAuthorizationCode("string", publicClientId2,
+ "https://public.client.com/redirect", "search", state,
+ userAuthHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+
+ assertEquals("https://public.client.com/redirect?"
+ + "error=unsupported_response_type"
+ + "&error_description=Unsupported+response+type.+Only+code+is+supported."
+ + "&state=thisIsMyState", response.getLocation().toString());
+
+ // with different redirect URI
+ String redirectUri = "https://different.uri/redirect";
+ response = requestAuthorizationCode("string", confidentialClientId,
+ redirectUri, "", state, userAuthHeader);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertEquals("Invalid redirect URI",
+ node.at("/error_description").asText());
+ assertEquals(state, node.at("/state").asText());
+
+ // without redirect URI in the request and no registered
+ // redirect URI
+ response = requestAuthorizationCode("string", publicClientId2, "", "",
+ state, userAuthHeader);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertEquals("Missing parameter: redirect URI",
+ node.at("/error_description").asText());
+ assertEquals(state, node.at("/state").asText());
+ }
+
+ @Test
+ public void testAuthorizeInvalidScope () throws KustvaktException {
+ String scope = "read_address";
+ Response response = requestAuthorizationCode("code",
+ confidentialClientId, "", scope, state, userAuthHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+
+ assertEquals(
+ "https://third.party.com/confidential/redirect?"
+ + "error=invalid_scope&error_description=Invalid+"
+ + "scope&state=thisIsMyState",
+ response.getLocation().toString());
+ }
+
+ @Test
+ public void testAuthorizeUnsupportedTokenResponseType ()
+ throws KustvaktException {
+ Response response = requestAuthorizationCode("token",
+ confidentialClientId, "", "search", state, userAuthHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+
+ assertEquals("https://third.party.com/confidential/redirect?"
+ + "error=unsupported_response_type"
+ + "&error_description=Unsupported+response+type.+Only+code+is+supported."
+ + "&state=thisIsMyState", response.getLocation().toString());
+ }
+
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
new file mode 100644
index 0000000..5f24cd6
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -0,0 +1,678 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.entity.ContentType;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+/**
+ * @author margaretha
+ */
+public class OAuth2ClientControllerTest extends OAuth2TestBase {
+
+ private String username = "OAuth2ClientControllerTest";
+
+ private String userAuthHeader;
+
+ public OAuth2ClientControllerTest () throws KustvaktException {
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ }
+
+ private OAuth2ClientJson createOAuth2ClientJson (String name,
+ OAuth2ClientType type, String description) {
+ OAuth2ClientJson client = new OAuth2ClientJson();
+ if (name != null) {
+ client.setName(name);
+ }
+ client.setType(type);
+ if (description != null) {
+ client.setDescription(description);
+ }
+ return client;
+ }
+
+ @Test
+ public void testRetrieveClientInfo () throws KustvaktException {
+ // public client
+ JsonNode clientInfo = retrieveClientInfo(publicClientId, "system");
+ assertEquals(publicClientId, clientInfo.at("/client_id").asText());
+ assertEquals(clientInfo.at("/client_name").asText(),
+ "public client plugin with redirect uri");
+ assertNotNull(clientInfo.at("/client_description"));
+ assertNotNull(clientInfo.at("/client_url"));
+ assertEquals(clientInfo.at("/client_type").asText(), "PUBLIC");
+ assertEquals(clientInfo.at("/registered_by").asText(), "system");
+ // confidential client
+ clientInfo = retrieveClientInfo(confidentialClientId, "system");
+ assertEquals(confidentialClientId,
+ clientInfo.at("/client_id").asText());
+ assertEquals(clientInfo.at("/client_name").asText(),
+ "non super confidential client");
+ assertNotNull(clientInfo.at("/client_url"));
+ assertNotNull(clientInfo.at("/redirect_uri"));
+ assertEquals(false, clientInfo.at("/super").asBoolean());
+ assertEquals(clientInfo.at("/client_type").asText(), "CONFIDENTIAL");
+ // super client
+ clientInfo = retrieveClientInfo(superClientId, "system");
+ assertEquals(superClientId, clientInfo.at("/client_id").asText());
+ assertEquals(clientInfo.at("/client_name").asText(),
+ "super confidential client");
+ assertNotNull(clientInfo.at("/client_url"));
+ assertNotNull(clientInfo.at("/redirect_uri"));
+ assertEquals(clientInfo.at("/client_type").asText(), "CONFIDENTIAL");
+ assertTrue(clientInfo.at("/super").asBoolean());
+ }
+
+ @Test
+ public void testRegisterConfidentialClient () throws KustvaktException {
+ Response response = registerConfidentialClient(username);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ String clientId = node.at("/client_id").asText();
+ String clientSecret = node.at("/client_secret").asText();
+ assertNotNull(clientId);
+ assertNotNull(clientSecret);
+ assertFalse(clientId.contains("a"));
+ testListConfidentialClient(username, clientId);
+ testConfidentialClientInfo(clientId, username);
+ testResetConfidentialClientSecret(clientId, clientSecret);
+ deregisterClient(username, clientId);
+ }
+
+ @Test
+ public void testRegisterClientNameTooShort ()
+ throws ProcessingException, KustvaktException {
+ OAuth2ClientJson clientJson = createOAuth2ClientJson("R",
+ OAuth2ClientType.PUBLIC, null);
+ Response response = registerClient(username, clientJson);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/error_description").asText(),
+ "client_name must contain at least 3 characters");
+ assertEquals(node.at("/error").asText(), "invalid_request");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testRegisterClientEmptyName ()
+ throws ProcessingException, KustvaktException {
+ OAuth2ClientJson clientJson = createOAuth2ClientJson("",
+ OAuth2ClientType.PUBLIC, null);
+ Response response = registerClient(username, clientJson);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/error_description").asText(),
+ "client_name must contain at least 3 characters");
+ assertEquals(node.at("/error").asText(), "invalid_request");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testRegisterClientMissingName ()
+ throws ProcessingException, KustvaktException {
+ OAuth2ClientJson clientJson = createOAuth2ClientJson(null,
+ OAuth2ClientType.PUBLIC, null);
+ Response response = registerClient(username, clientJson);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/error_description").asText(),
+ "client_name is null");
+ assertEquals(node.at("/error").asText(), "invalid_request");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testRegisterClientMissingDescription ()
+ throws ProcessingException, KustvaktException {
+ OAuth2ClientJson clientJson = createOAuth2ClientJson("R client",
+ OAuth2ClientType.PUBLIC, null);
+ Response response = registerClient(username, clientJson);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/error_description").asText(),
+ "client_description is null");
+ assertEquals(node.at("/error").asText(), "invalid_request");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testRegisterClientMissingType ()
+ throws ProcessingException, KustvaktException {
+ OAuth2ClientJson clientJson = createOAuth2ClientJson("R client", null,
+ null);
+ Response response = registerClient(username, clientJson);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/error_description").asText(),
+ "client_type is null");
+ assertEquals(node.at("/error").asText(), "invalid_request");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testRegisterClientInvalidRedirectURI ()
+ throws ProcessingException, KustvaktException {
+ // invalid hostname
+ String redirectUri = "https://test.public.client/redirect";
+ OAuth2ClientJson clientJson = createOAuth2ClientJson(
+ "OAuth2PublicClient", OAuth2ClientType.PUBLIC,
+ "A public test client.");
+ clientJson.setRedirectURI(redirectUri);
+ Response response = registerClient(username, clientJson);
+ testInvalidRedirectUri(response.readEntity(String.class),
+ response.getHeaderString("Content-Type"), false,
+ response.getStatus());
+ // localhost is not allowed
+ // redirectUri = "http://localhost:1410";
+ // clientJson.setRedirectURI(redirectUri);
+ // response = registerClient(username, clientJson);
+ // testInvalidRedirectUri(response.readEntity(String.class), false,
+ // response.getStatus());
+ // fragment is not allowed
+ redirectUri = "https://public.client.com/redirect.html#bar";
+ clientJson.setRedirectURI(redirectUri);
+ response = registerClient(username, clientJson);
+ testInvalidRedirectUri(response.readEntity(String.class),
+ response.getHeaderString("Content-Type"), false,
+ response.getStatus());
+ }
+
+ @Test
+ public void testRegisterPublicClientWithRefreshTokenExpiry ()
+ throws ProcessingException, KustvaktException {
+ OAuth2ClientJson clientJson = createOAuth2ClientJson(
+ "OAuth2PublicClient", OAuth2ClientType.PUBLIC,
+ "A public test client.");
+ clientJson.setRefreshTokenExpiry(31535000);
+ Response response = registerClient(username, clientJson);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(node.at("/error").asText(), "invalid_request");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testRegisterConfidentialClientWithRefreshTokenExpiry ()
+ throws ProcessingException, KustvaktException {
+ int expiry = 31535000;
+ OAuth2ClientJson clientJson = createOAuth2ClientJson(
+ "OAuth2 Confidential Client", OAuth2ClientType.CONFIDENTIAL,
+ "A confidential client.");
+ clientJson.setRefreshTokenExpiry(expiry);
+ Response response = registerClient(username, clientJson);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String clientId = node.at("/client_id").asText();
+ JsonNode clientInfo = retrieveClientInfo(clientId, username);
+ assertEquals(expiry, clientInfo.at("/refresh_token_expiry").asInt());
+ deregisterClient(username, clientId);
+ }
+
+ @Test
+ public void testRegisterConfidentialClientWithInvalidRefreshTokenExpiry ()
+ throws ProcessingException, KustvaktException {
+ OAuth2ClientJson clientJson = createOAuth2ClientJson(
+ "OAuth2 Confidential Client", OAuth2ClientType.CONFIDENTIAL,
+ "A confidential client.");
+ clientJson.setRefreshTokenExpiry(31537000);
+ Response response = registerClient(username, clientJson);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(node.at("/error_description").asText(),
+ "Maximum refresh token expiry is 31536000 seconds (1 year)");
+ assertEquals(node.at("/error").asText(), "invalid_request");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testRegisterClientInvalidURL ()
+ throws ProcessingException, KustvaktException {
+ // invalid hostname
+ String url = "https://test.public.client";
+ OAuth2ClientJson clientJson = createOAuth2ClientJson(
+ "OAuth2PublicClient", OAuth2ClientType.PUBLIC,
+ "A public test client.");
+ clientJson.setUrl(url);
+ Response response = registerClient(username, clientJson);
+ testInvalidUrl(response.readEntity(String.class), response.getStatus());
+ // localhost is not allowed
+ url = "http://localhost:1410";
+ clientJson.setRedirectURI(url);
+ response = registerClient(username, clientJson);
+ testInvalidUrl(response.readEntity(String.class), response.getStatus());
+ }
+
+ private void testInvalidUrl (String entity, int status)
+ throws KustvaktException {
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertEquals(node.at("/error_description").asText(), "Invalid URL");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), status);
+ }
+
+ @Test
+ public void testRegisterPublicClient ()
+ throws ProcessingException, KustvaktException {
+ String redirectUri = "https://public.client.com/redirect";
+ OAuth2ClientJson clientJson = createOAuth2ClientJson(
+ "OAuth2PublicClient", OAuth2ClientType.PUBLIC,
+ "A public test client.");
+ // http and fragment are allowed
+ clientJson.setUrl("http://public.client.com/index.html#bar");
+ clientJson.setRedirectURI(redirectUri);
+ Response response = registerClient(username, clientJson);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ String clientId = node.at("/client_id").asText();
+ assertNotNull(clientId);
+ assertTrue(node.at("/client_secret").isMissingNode());
+ testRegisterClientUnauthorizedScope(clientId);
+ testResetPublicClientSecret(clientId);
+ testAccessTokenAfterDeregistration(clientId, null, "");
+ }
+
+ private void testRegisterClientUnauthorizedScope (String clientId)
+ throws ProcessingException, KustvaktException {
+ String userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ String code = requestAuthorizationCode(clientId, userAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(clientId,
+ clientSecret, code);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(node.at("/scope").asText(), "match_info search");
+ String accessToken = node.at("/access_token").asText();
+ OAuth2ClientJson clientJson = createOAuth2ClientJson("R client",
+ OAuth2ClientType.PUBLIC, null);
+ response = target().path(API_VERSION).path("oauth2").path("client")
+ .path("register").request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .post(Entity.json(clientJson));
+ String entity = response.readEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Scope register_client is not authorized");
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testRegisterClientUsingPlainJson ()
+ throws ProcessingException, KustvaktException, IOException {
+ InputStream is = getClass().getClassLoader()
+ .getResourceAsStream("json/oauth2_public_client.json");
+ String json = IOUtils.toString(is, Charset.defaultCharset());
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("register").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username,
+ "password"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .post(Entity.json(json));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ String clientId = node.at("/client_id").asText();
+ assertNotNull(clientId);
+ assertTrue(node.at("/client_secret").isMissingNode());
+ testResetPublicClientSecret(clientId);
+ testAccessTokenAfterDeregistration(clientId, null, "");
+ }
+
+ @Test
+ public void testRegisterDesktopApp ()
+ throws ProcessingException, KustvaktException {
+ OAuth2ClientJson clientJson = createOAuth2ClientJson(
+ "OAuth2DesktopClient", OAuth2ClientType.PUBLIC,
+ "This is a desktop test client.");
+ Response response = registerClient(username, clientJson);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ String clientId = node.at("/client_id").asText();
+ assertNotNull(clientId);
+ assertTrue(node.at("/client_secret").isMissingNode());
+ testDeregisterPublicClientMissingUserAuthentication(clientId);
+ testDeregisterPublicClientMissingId();
+ testDeregisterPublicClient(clientId, username);
+ }
+
+ @Test
+ public void testRegisterMultipleDesktopApps ()
+ throws ProcessingException, KustvaktException {
+ // First client
+ OAuth2ClientJson clientJson = createOAuth2ClientJson(
+ "OAuth2DesktopClient1", OAuth2ClientType.PUBLIC,
+ "A desktop test client.");
+ Response response = registerClient(username, clientJson);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ String clientId1 = node.at("/client_id").asText();
+ assertNotNull(clientId1);
+ assertTrue(node.at("/client_secret").isMissingNode());
+ // Second client
+ clientJson = createOAuth2ClientJson("OAuth2DesktopClient2",
+ OAuth2ClientType.PUBLIC, "Another desktop test client.");
+ response = registerClient(username, clientJson);
+ entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(entity);
+ String clientId2 = node.at("/client_id").asText();
+ assertNotNull(clientId2);
+ assertTrue(node.at("/client_secret").isMissingNode());
+ testResetPublicClientSecret(clientId1);
+ testAccessTokenAfterDeregistration(clientId1, null,
+ "https://OAuth2DesktopClient1.com");
+ testResetPublicClientSecret(clientId2);
+ testAccessTokenAfterDeregistration(clientId2, null,
+ "https://OAuth2DesktopClient2.com");
+ }
+
+ private void testAccessTokenAfterDeregistration (String clientId,
+ String clientSecret, String redirectUri) throws KustvaktException {
+ String userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ String code = requestAuthorizationCode(clientId, redirectUri,
+ userAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(clientId,
+ clientSecret, code, redirectUri);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String accessToken = node.at("/access_token").asText();
+ response = searchWithAccessToken(accessToken);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ code = requestAuthorizationCode(clientId, redirectUri, userAuthHeader);
+ testDeregisterPublicClient(clientId, username);
+ response = requestTokenWithAuthorizationCodeAndForm(clientId,
+ clientSecret, code, redirectUri);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(OAuth2Error.INVALID_CLIENT.toString(),
+ node.at("/error").asText());
+ response = searchWithAccessToken(accessToken);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.INVALID_ACCESS_TOKEN,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Access token is invalid");
+ }
+
+ private void testDeregisterPublicClientMissingUserAuthentication (
+ String clientId) throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("deregister").path(clientId).request()
+ .delete();
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ }
+
+ private void testDeregisterPublicClientMissingId ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("deregister").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .delete();
+ assertEquals(Status.METHOD_NOT_ALLOWED.getStatusCode(),
+ response.getStatus());
+ }
+
+ private void testDeregisterPublicClient (String clientId, String username)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("deregister").path(clientId).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ private void testResetPublicClientSecret (String clientId)
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("client_id", clientId);
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("reset").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertEquals(node.at("/error_description").asText(),
+ "Operation is not allowed for public clients");
+ }
+
+ private String testResetConfidentialClientSecret (String clientId,
+ String clientSecret) throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("client_id", clientId);
+ form.param("client_secret", clientSecret);
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("reset").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(clientId, node.at("/client_id").asText());
+ String newClientSecret = node.at("/client_secret").asText();
+ assertTrue(!clientSecret.equals(newClientSecret));
+ return newClientSecret;
+ }
+
+ private void requestAuthorizedClientList (String userAuthHeader)
+ throws KustvaktException {
+ Form form = getSuperClientForm();
+ form.param("authorized_only", "true");
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("list").request()
+ .header(Attributes.AUTHORIZATION, userAuthHeader)
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.size());
+ assertEquals(confidentialClientId, node.at("/0/client_id").asText());
+ assertEquals(publicClientId, node.at("/1/client_id").asText());
+ assertEquals(node.at("/0/client_name").asText(),
+ "non super confidential client");
+ assertEquals(node.at("/0/client_type").asText(), "CONFIDENTIAL");
+ assertFalse(node.at("/0/client_url").isMissingNode());
+ assertFalse(node.at("/0/client_description").isMissingNode());
+ }
+
+ @Test
+ public void testListPublicClient () throws KustvaktException {
+ String clientName = "OAuth2DoryClient";
+ OAuth2ClientJson json = createOAuth2ClientJson(clientName,
+ OAuth2ClientType.PUBLIC, "Dory's client.");
+ registerClient("dory", json);
+ JsonNode node = listUserRegisteredClients("dory");
+ assertEquals(1, node.size());
+ assertEquals(clientName, node.at("/0/client_name").asText());
+ assertEquals(OAuth2ClientType.PUBLIC.name(),
+ node.at("/0/client_type").asText());
+ assertTrue(node.at("/0/permitted").asBoolean());
+ assertFalse(node.at("/0/registration_date").isMissingNode());
+ assertTrue(node.at("/refresh_token_expiry").isMissingNode());
+ String clientId = node.at("/0/client_id").asText();
+ testDeregisterPublicClient(clientId, "dory");
+ }
+
+ private void testListConfidentialClient (String username, String clientId)
+ throws ProcessingException, KustvaktException {
+ JsonNode node = listUserRegisteredClients(username);
+ assertEquals(1, node.size());
+ assertEquals(clientId, node.at("/0/client_id").asText());
+ assertEquals(node.at("/0/client_name").asText(), "OAuth2ClientTest");
+ assertEquals(OAuth2ClientType.CONFIDENTIAL.name(),
+ node.at("/0/client_type").asText());
+ assertNotNull(node.at("/0/client_description"));
+ assertEquals(clientURL, node.at("/0/client_url").asText());
+ assertEquals(clientRedirectUri,
+ node.at("/0/client_redirect_uri").asText());
+ assertNotNull(node.at("/0/registration_date"));
+ assertEquals(defaultRefreshTokenExpiry,
+ node.at("/0/refresh_token_expiry").asInt());
+ assertTrue(node.at("/0/permitted").asBoolean());
+ assertTrue(node.at("/0/source").isMissingNode());
+ }
+
+ @Test
+ public void testListUserClients () throws KustvaktException {
+ String username = "pearl";
+ String password = "pwd";
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, password);
+ // super client
+ Response response = requestTokenWithPassword(superClientId,
+ clientSecret, username, password);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // client 1
+ String code = requestAuthorizationCode(publicClientId, userAuthHeader);
+ response = requestTokenWithAuthorizationCodeAndForm(publicClientId, "",
+ code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String accessToken = node.at("/access_token").asText();
+ // client 2
+ code = requestAuthorizationCode(confidentialClientId, userAuthHeader);
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ String refreshToken = node.at("/refresh_token").asText();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ requestAuthorizedClientList(userAuthHeader);
+ testListAuthorizedClientWithMultipleRefreshTokens(userAuthHeader);
+ testListAuthorizedClientWithMultipleAccessTokens(userAuthHeader);
+ testListWithClientsFromAnotherUser(userAuthHeader);
+ // revoke client 1
+ testRevokeAllTokenViaSuperClient(publicClientId, userAuthHeader,
+ accessToken);
+ // revoke client 2
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ accessToken = node.at("/access_token").asText();
+ refreshToken = node.at("/refresh_token").asText();
+ testRevokeAllTokenViaSuperClient(confidentialClientId, userAuthHeader,
+ accessToken);
+ testRequestTokenWithRevokedRefreshToken(confidentialClientId,
+ clientSecret, refreshToken);
+ }
+
+ private void testListAuthorizedClientWithMultipleRefreshTokens (
+ String userAuthHeader) throws KustvaktException {
+ // client 2
+ String code = requestAuthorizationCode(confidentialClientId,
+ userAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ requestAuthorizedClientList(userAuthHeader);
+ }
+
+ private void testListAuthorizedClientWithMultipleAccessTokens (
+ String userAuthHeader) throws KustvaktException {
+ // client 1
+ String code = requestAuthorizationCode(publicClientId, userAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ publicClientId, "", code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ requestAuthorizedClientList(userAuthHeader);
+ }
+
+ private void testListWithClientsFromAnotherUser (String userAuthHeader)
+ throws KustvaktException {
+ String aaaAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("aaa", "pwd");
+ // client 1
+ String code = requestAuthorizationCode(publicClientId, aaaAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ publicClientId, "", code);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String accessToken1 = node.at("/access_token").asText();
+ // client 2
+ code = requestAuthorizationCode(confidentialClientId, aaaAuthHeader);
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ String accessToken2 = node.at("/access_token").asText();
+ String refreshToken = node.at("/refresh_token").asText();
+ requestAuthorizedClientList(aaaAuthHeader);
+ requestAuthorizedClientList(userAuthHeader);
+ testRevokeAllTokenViaSuperClient(publicClientId, aaaAuthHeader,
+ accessToken1);
+ testRevokeAllTokenViaSuperClient(confidentialClientId, aaaAuthHeader,
+ accessToken2);
+ testRequestTokenWithRevokedRefreshToken(confidentialClientId,
+ clientSecret, refreshToken);
+ }
+
+ private void testRevokeAllTokenViaSuperClient (String clientId,
+ String userAuthHeader, String accessToken)
+ throws KustvaktException {
+ // check token before revoking
+ Response response = searchWithAccessToken(accessToken);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertTrue(node.at("/matches").size() > 0);
+ Form form = getSuperClientForm();
+ form.param("client_id", clientId);
+ response = target().path(API_VERSION).path("oauth2").path("revoke")
+ .path("super").path("all").request()
+ .header(Attributes.AUTHORIZATION, userAuthHeader)
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ assertEquals(response.readEntity(String.class), "SUCCESS");
+ response = searchWithAccessToken(accessToken);
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.INVALID_ACCESS_TOKEN,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Access token is invalid");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
new file mode 100644
index 0000000..ab2eb27
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
@@ -0,0 +1,730 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.ZonedDateTime;
+import java.util.Set;
+
+import org.apache.http.entity.ContentType;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import com.nimbusds.oauth2.sdk.GrantType;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.constant.TokenType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+/**
+ * @author margaretha
+ */
+public class OAuth2ControllerTest extends OAuth2TestBase {
+
+ @Autowired
+ public FullConfiguration config;
+
+ public String userAuthHeader;
+
+ public OAuth2ControllerTest () throws KustvaktException {
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ }
+
+ @Test
+ public void testRequestTokenAuthorizationPublic ()
+ throws KustvaktException {
+ String code = requestAuthorizationCode(publicClientId, userAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ publicClientId, clientSecret, code);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ String accessToken = node.at("/access_token").asText();
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertEquals(31536000, node.at("/expires_in").asInt());
+ testRevokeToken(accessToken, publicClientId, null, ACCESS_TOKEN_TYPE);
+ assertTrue(node.at("/refresh_token").isMissingNode());
+ }
+
+ @Test
+ public void testRequestTokenAuthorizationConfidential ()
+ throws KustvaktException {
+ String scope = "search";
+ Response response = requestAuthorizationCode("code",
+ confidentialClientId, "", scope, state, userAuthHeader);
+ MultivaluedMap<String, String> params = getQueryParamsFromURI(
+ response.getLocation());
+ String code = params.get("code").get(0);
+
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node.at("/access_token").asText());
+ assertNotNull(node.at("/refresh_token").asText());
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ testRequestTokenWithUsedAuthorization(code);
+ String refreshToken = node.at("/refresh_token").asText();
+ testRefreshTokenExpiry(refreshToken);
+ testRequestRefreshTokenInvalidScope(confidentialClientId, refreshToken);
+ testRequestRefreshTokenInvalidClient(refreshToken);
+ testRequestRefreshTokenInvalidRefreshToken(confidentialClientId);
+ testRequestRefreshToken(confidentialClientId, clientSecret,
+ refreshToken);
+ }
+
+ private void testRequestTokenWithUsedAuthorization (String code)
+ throws KustvaktException {
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
+ assertEquals(node.at("/error_description").asText(),
+ "Invalid authorization");
+ }
+
+ @Test
+ public void testRequestTokenInvalidAuthorizationCode ()
+ throws KustvaktException {
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, "blahblah");
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ }
+
+ @Test
+ public void testRequestTokenAuthorizationReplyAttack ()
+ throws KustvaktException {
+ String redirect_uri = "https://third.party.com/confidential/redirect";
+ String scope = "search";
+ Response response = requestAuthorizationCode("code",
+ confidentialClientId, redirect_uri, scope, state,
+ userAuthHeader);
+ String code = parseAuthorizationCode(response);
+ testRequestTokenAuthorizationInvalidClient(code);
+ testRequestTokenAuthorizationMissingRedirectUri(code);
+ testRequestTokenAuthorizationInvalidRedirectUri(code);
+ testRequestTokenAuthorizationRevoked(code, redirect_uri);
+ }
+
+ private void testRequestTokenAuthorizationInvalidClient (String code)
+ throws KustvaktException {
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, "wrong_secret", code);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_CLIENT, node.at("/error").asText());
+ }
+
+ private void testRequestTokenAuthorizationMissingRedirectUri (String code)
+ throws KustvaktException {
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, "secret", code);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
+ assertEquals(node.at("/error_description").asText(),
+ "Missing redirect URI");
+ }
+
+ private void testRequestTokenAuthorizationInvalidRedirectUri (String code)
+ throws KustvaktException {
+ Form tokenForm = new Form();
+ tokenForm.param("grant_type", "authorization_code");
+ tokenForm.param("client_id", confidentialClientId);
+ tokenForm.param("client_secret", "secret");
+ tokenForm.param("code", code);
+ tokenForm.param("redirect_uri", "https://blahblah.com");
+ Response response = requestToken(tokenForm);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
+ }
+
+ private void testRequestTokenAuthorizationRevoked (String code, String uri)
+ throws KustvaktException {
+ Form tokenForm = new Form();
+ tokenForm.param("grant_type", "authorization_code");
+ tokenForm.param("client_id", confidentialClientId);
+ tokenForm.param("client_secret", "secret");
+ tokenForm.param("code", code);
+ tokenForm.param("redirect_uri", uri);
+ Response response = requestToken(tokenForm);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
+ assertEquals(node.at("/error_description").asText(),
+ "Invalid authorization");
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantConfidentialSuper ()
+ throws KustvaktException {
+ Response response = requestTokenWithDoryPassword(superClientId,
+ clientSecret);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node.at("/access_token").asText());
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ assertEquals(node.at("/scope").asText(), "all");
+ String refresh = node.at("/refresh_token").asText();
+ RefreshToken refreshToken = refreshTokenDao
+ .retrieveRefreshToken(refresh);
+ Set<AccessScope> scopes = refreshToken.getScopes();
+ assertEquals(1, scopes.size());
+ assertEquals(scopes.toString(), "[all]");
+ testRefreshTokenExpiry(refresh);
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantWithScope ()
+ throws KustvaktException {
+ String scope = "match_info search";
+ Form form = new Form();
+ form.param("grant_type", "password");
+ form.param("client_id", superClientId);
+ form.param("client_secret", clientSecret);
+ form.param("username", "dory");
+ form.param("password", "pwd");
+ form.param("scope", scope);
+ Response response = requestToken(form);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node.at("/access_token").asText());
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ assertEquals(scope, node.at("/scope").asText());
+ String refreshToken = node.at("/refresh_token").asText();
+ testRequestRefreshTokenWithUnauthorizedScope(superClientId,
+ clientSecret, refreshToken, "all");
+ testRequestRefreshTokenWithScope(superClientId, clientSecret,
+ refreshToken, "search");
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantConfidentialNonSuper ()
+ throws KustvaktException {
+ Response response = requestTokenWithDoryPassword(confidentialClientId,
+ clientSecret);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.UNAUTHORIZED_CLIENT,
+ node.at("/error").asText());
+ assertEquals(node.at("/error_description").asText(),
+ "Password grant is not allowed for third party clients");
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantPublic ()
+ throws KustvaktException {
+ Response response = requestTokenWithDoryPassword(publicClientId, "");
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.UNAUTHORIZED_CLIENT,
+ node.at("/error").asText());
+ assertEquals(node.at("/error_description").asText(),
+ "Password grant is not allowed for third party clients");
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantAuthorizationHeader ()
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", "password");
+ form.param("client_id", superClientId);
+ form.param("username", "dory");
+ form.param("password", "password");
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.AUTHORIZATION,
+ "Basic ZkNCYlFrQXlZekk0TnpVeE1nOnNlY3JldA==")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node.at("/access_token").asText());
+ assertNotNull(node.at("/refresh_token").asText());
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ }
+
+ /**
+ * In case, client_id is specified both in Authorization header
+ * and request body, client_id in the request body is ignored.
+ *
+ * @throws KustvaktException
+ */
+ @Test
+ public void testRequestTokenPasswordGrantDifferentClientIds ()
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", "password");
+ form.param("client_id", "9aHsGW6QflV13ixNpez");
+ form.param("username", "dory");
+ form.param("password", "password");
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.AUTHORIZATION,
+ "Basic ZkNCYlFrQXlZekk0TnpVeE1nOnNlY3JldA==")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node.at("/access_token").asText());
+ assertNotNull(node.at("/refresh_token").asText());
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantMissingClientSecret ()
+ throws KustvaktException {
+ Response response = requestTokenWithDoryPassword(confidentialClientId,
+ "");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertNotNull(node.at("/error_description").asText());
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantEmptyClientSecret ()
+ throws KustvaktException {
+ Response response = requestTokenWithDoryPassword(confidentialClientId,
+ "");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertEquals(node.at("/error_description").asText(),
+ "Missing parameter: client_secret");
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantMissingClientId ()
+ throws KustvaktException {
+ Response response = requestTokenWithDoryPassword(null, clientSecret);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertNotNull(node.at("/error_description").asText());
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantEmptyClientId ()
+ throws KustvaktException {
+ Response response = requestTokenWithDoryPassword("", clientSecret);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertNotNull(node.at("/error_description").asText());
+ }
+
+ @Test
+ public void testRequestTokenClientCredentialsGrant ()
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", "client_credentials");
+ form.param("client_id", confidentialClientId);
+ form.param("client_secret", "secret");
+ Response response = requestToken(form);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ // length?
+ assertNotNull(node.at("/access_token").asText());
+ assertNotNull(node.at("/refresh_token").asText());
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ }
+
+ /**
+ * Client credentials grant is only allowed for confidential
+ * clients.
+ */
+ @Test
+ public void testRequestTokenClientCredentialsGrantPublic ()
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", "client_credentials");
+ form.param("client_id", publicClientId);
+ form.param("client_secret", "");
+ Response response = requestToken(form);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertNotNull(node.at("/error_description").asText());
+ }
+
+ @Test
+ public void testRequestTokenClientCredentialsGrantReducedScope ()
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", "client_credentials");
+ form.param("client_id", confidentialClientId);
+ form.param("client_secret", "secret");
+ form.param("scope", "preferred_username client_info");
+ Response response = requestToken(form);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ // length?
+ assertNotNull(node.at("/access_token").asText());
+ assertNotNull(node.at("/refresh_token").asText());
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ assertEquals(node.at("/scope").asText(), "client_info");
+ }
+
+ @Test
+ public void testRequestTokenMissingGrantType () throws KustvaktException {
+ Form form = new Form();
+ Response response = requestToken(form);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ }
+
+ @Test
+ public void testRequestTokenUnsupportedGrant () throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", "blahblah");
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node.get("error_description").asText());
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.get("error").asText());
+ }
+
+ private void testRequestRefreshTokenInvalidScope (String clientId,
+ String refreshToken) throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+ form.param("client_id", clientId);
+ form.param("client_secret", clientSecret);
+ form.param("refresh_token", refreshToken);
+ form.param("scope", "search serialize_query");
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_SCOPE, node.at("/error").asText());
+ }
+
+ private void testRequestRefreshToken (String clientId, String clientSecret,
+ String refreshToken) throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+ form.param("client_id", clientId);
+ form.param("client_secret", clientSecret);
+ form.param("refresh_token", refreshToken);
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node.at("/access_token").asText());
+ String newRefreshToken = node.at("/refresh_token").asText();
+ assertNotNull(newRefreshToken);
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ assertTrue(!newRefreshToken.equals(refreshToken));
+ testRequestTokenWithRevokedRefreshToken(clientId, clientSecret,
+ refreshToken);
+ testRevokeToken(newRefreshToken, clientId, clientSecret,
+ REFRESH_TOKEN_TYPE);
+ testRequestTokenWithRevokedRefreshToken(clientId, clientSecret,
+ newRefreshToken);
+ }
+
+ private void testRequestRefreshTokenWithUnauthorizedScope (String clientId,
+ String clientSecret, String refreshToken, String scope)
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+ form.param("client_id", clientId);
+ form.param("client_secret", clientSecret);
+ form.param("refresh_token", refreshToken);
+ form.param("scope", scope);
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_SCOPE, node.at("/error").asText());
+ }
+
+ private void testRequestRefreshTokenWithScope (String clientId,
+ String clientSecret, String refreshToken, String scope)
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+ form.param("client_id", clientId);
+ form.param("client_secret", clientSecret);
+ form.param("refresh_token", refreshToken);
+ form.param("scope", scope);
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node.at("/access_token").asText());
+ String newRefreshToken = node.at("/refresh_token").asText();
+ assertNotNull(newRefreshToken);
+ assertEquals(TokenType.BEARER.displayName(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ assertTrue(!newRefreshToken.equals(refreshToken));
+ assertEquals(scope, node.at("/scope").asText());
+ }
+
+ private void testRequestRefreshTokenInvalidClient (String refreshToken)
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+ form.param("client_id", "iBr3LsTCxOj7D2o0A5m");
+ form.param("refresh_token", refreshToken);
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_CLIENT, node.at("/error").asText());
+ }
+
+ private void testRequestRefreshTokenInvalidRefreshToken (String clientId)
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+ form.param("client_id", clientId);
+ form.param("client_secret", clientSecret);
+ form.param("refresh_token", "Lia8s8w8tJeZSBlaQDrYV8ion3l");
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
+ }
+
+ private JsonNode requestTokenList (String userAuthHeader, String tokenType,
+ String clientId) throws KustvaktException {
+ Form form = new Form();
+ form.param("super_client_id", superClientId);
+ form.param("super_client_secret", clientSecret);
+ form.param("token_type", tokenType);
+ if (clientId != null && !clientId.isEmpty()) {
+ form.param("client_id", clientId);
+ }
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").path("list").request()
+ .header(Attributes.AUTHORIZATION, userAuthHeader)
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+
+ private JsonNode requestTokenList (String userAuthHeader, String tokenType)
+ throws KustvaktException {
+ return requestTokenList(userAuthHeader, tokenType, null);
+ }
+
+ @Test
+ public void testListRefreshTokenConfidentialClient ()
+ throws KustvaktException {
+ String username = "gurgle";
+ String password = "pwd";
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, password);
+ // super client
+ Response response = requestTokenWithPassword(superClientId,
+ clientSecret, username, password);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String refreshToken1 = node.at("/refresh_token").asText();
+ // client 1
+ String code = requestAuthorizationCode(confidentialClientId,
+ userAuthHeader);
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // client 2
+ code = requestAuthorizationCode(confidentialClientId2,
+ clientRedirectUri, userAuthHeader);
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId2, clientSecret, code, clientRedirectUri);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // list
+ node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
+ assertEquals(2, node.size());
+ assertEquals(confidentialClientId, node.at("/0/client_id").asText());
+ assertEquals(confidentialClientId2, node.at("/1/client_id").asText());
+ // client 1
+ code = requestAuthorizationCode(confidentialClientId, userAuthHeader);
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // another user
+ String darlaAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("darla", "pwd");
+ // test listing clients
+ node = requestTokenList(darlaAuthHeader, REFRESH_TOKEN_TYPE);
+ assertEquals(0, node.size());
+ // client 1
+ code = requestAuthorizationCode(confidentialClientId, darlaAuthHeader);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ String refreshToken5 = node.at("/refresh_token").asText();
+ // list all refresh tokens
+ node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
+ assertEquals(3, node.size());
+ // list refresh tokens from client 1
+ node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE,
+ confidentialClientId);
+ assertEquals(2, node.size());
+ testRevokeToken(refreshToken1, superClientId, clientSecret,
+ REFRESH_TOKEN_TYPE);
+ testRevokeToken(node.at("/0/token").asText(), confidentialClientId,
+ clientSecret, REFRESH_TOKEN_TYPE);
+ testRevokeToken(node.at("/1/token").asText(), confidentialClientId2,
+ clientSecret, REFRESH_TOKEN_TYPE);
+ node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
+ assertEquals(1, node.size());
+ testRevokeTokenViaSuperClient(node.at("/0/token").asText(),
+ userAuthHeader);
+ node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
+ assertEquals(0, node.size());
+ // try revoking a token belonging to another user
+ // should not return any errors
+ testRevokeTokenViaSuperClient(refreshToken5, userAuthHeader);
+ node = requestTokenList(darlaAuthHeader, REFRESH_TOKEN_TYPE);
+ assertEquals(1, node.size());
+ testRevokeTokenViaSuperClient(refreshToken5, darlaAuthHeader);
+ node = requestTokenList(darlaAuthHeader, REFRESH_TOKEN_TYPE);
+ assertEquals(0, node.size());
+ }
+
+ @Test
+ public void testListTokenPublicClient () throws KustvaktException {
+ String username = "nemo";
+ String password = "pwd";
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, password);
+ // access token 1
+ String code = requestAuthorizationCode(publicClientId, userAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(
+ publicClientId, "", code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String accessToken1 = node.at("/access_token").asText();
+ // access token 2
+ code = requestAuthorizationCode(publicClientId, userAuthHeader);
+ response = requestTokenWithAuthorizationCodeAndForm(publicClientId, "",
+ code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ String accessToken2 = node.at("/access_token").asText();
+ // list access tokens
+ node = requestTokenList(userAuthHeader, ACCESS_TOKEN_TYPE);
+ assertEquals(2, node.size());
+ // list refresh tokens
+ node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
+ assertEquals(0, node.size());
+ testRevokeTokenViaSuperClient(accessToken1, userAuthHeader);
+ node = requestTokenList(userAuthHeader, ACCESS_TOKEN_TYPE);
+ // System.out.println(node);
+ assertEquals(1, node.size());
+ assertEquals(accessToken2, node.at("/0/token").asText());
+ assertTrue(node.at("/0/scope").size() > 0);
+ assertNotNull(node.at("/0/created_date").asText());
+ assertNotNull(node.at("/0/expires_in").asLong());
+ assertNotNull(node.at("/0/user_authentication_time").asText());
+ assertEquals(publicClientId, node.at("/0/client_id").asText());
+ assertNotNull(node.at("/0/client_name").asText());
+ assertNotNull(node.at("/0/client_description").asText());
+ assertNotNull(node.at("/0/client_url").asText());
+ testRevokeTokenViaSuperClient(accessToken2, userAuthHeader);
+ node = requestTokenList(userAuthHeader, ACCESS_TOKEN_TYPE);
+ assertEquals(0, node.size());
+ }
+
+ private void testRefreshTokenExpiry (String refreshToken)
+ throws KustvaktException {
+ RefreshToken token = refreshTokenDao.retrieveRefreshToken(refreshToken);
+ ZonedDateTime expiry = token.getCreatedDate()
+ .plusSeconds(config.getRefreshTokenLongExpiry());
+ assertTrue(expiry.equals(token.getExpiryDate()));
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java
new file mode 100644
index 0000000..90597e2
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java
@@ -0,0 +1,527 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.http.entity.ContentType;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.entity.InstalledPlugin;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.dao.InstalledPluginDao;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
+
+public class OAuth2PluginTest extends OAuth2TestBase {
+
+ private String username = "plugin-user";
+
+ @Autowired
+ private InstalledPluginDao pluginDao;
+
+ @Test
+ public void testRegisterPlugin ()
+ throws ProcessingException, KustvaktException {
+ JsonNode source = JsonUtils.readTree("{ \"plugin\" : \"source\"}");
+ int refreshTokenExpiry = TimeUtils.convertTimeToSeconds("90D");
+ String clientName = "Plugin";
+ OAuth2ClientJson json = new OAuth2ClientJson();
+ json.setName(clientName);
+ json.setType(OAuth2ClientType.CONFIDENTIAL);
+ json.setDescription("This is a plugin test client.");
+ json.setSource(source);
+ json.setRefreshTokenExpiry(refreshTokenExpiry);
+ Response response = registerClient(username, json);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String clientId = node.at("/client_id").asText();
+ String clientSecret = node.at("/client_secret").asText();
+ assertNotNull(clientId);
+ assertNotNull(clientSecret);
+ testInstallPluginNotPermitted(clientId);
+ testRetrievePluginInfo(clientId, refreshTokenExpiry);
+ node = listPlugins(false);
+ assertEquals(3, node.size());
+ // permitted only
+ node = listPlugins(true);
+ assertEquals(2, node.size());
+ testListUserRegisteredPlugins(username, clientId, clientName,
+ refreshTokenExpiry);
+ deregisterClient(username, clientId);
+ }
+
+ @Test
+ public void testRegisterPublicPlugin () throws KustvaktException {
+ JsonNode source = JsonUtils.readTree("{ \"plugin\" : \"source\"}");
+ String clientName = "Public Plugin";
+ OAuth2ClientJson json = new OAuth2ClientJson();
+ json.setName(clientName);
+ json.setType(OAuth2ClientType.PUBLIC);
+ json.setDescription("This is a public plugin.");
+ json.setSource(source);
+ Response response = registerClient(username, json);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertFalse(node.at("/error_description").isMissingNode());
+ // assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // String clientId = node.at("/client_id").asText();
+ // assertTrue(node.at("/client_secret").isMissingNode());
+ //
+ // deregisterClient(username, clientId);
+ }
+
+ private void testRetrievePluginInfo (String clientId,
+ int refreshTokenExpiry)
+ throws ProcessingException, KustvaktException {
+ JsonNode clientInfo = retrieveClientInfo(clientId, username);
+ assertEquals(clientId, clientInfo.at("/client_id").asText());
+ assertEquals(clientInfo.at("/client_name").asText(), "Plugin");
+ assertEquals(OAuth2ClientType.CONFIDENTIAL.name(),
+ clientInfo.at("/client_type").asText());
+ assertNotNull(clientInfo.at("/client_description").asText());
+ assertNotNull(clientInfo.at("/source").asText());
+ assertFalse(clientInfo.at("/permitted").asBoolean());
+ assertEquals(username, clientInfo.at("/registered_by").asText());
+ assertNotNull(clientInfo.at("/registration_date"));
+ assertEquals(refreshTokenExpiry,
+ clientInfo.at("/refresh_token_expiry").asInt());
+ }
+
+ private void testListUserRegisteredPlugins (String username,
+ String clientId, String clientName, int refreshTokenExpiry)
+ throws ProcessingException, KustvaktException {
+ JsonNode node = listUserRegisteredClients(username);
+ assertEquals(1, node.size());
+ assertEquals(clientId, node.at("/0/client_id").asText());
+ assertEquals(clientName, node.at("/0/client_name").asText());
+ assertEquals(OAuth2ClientType.CONFIDENTIAL.name(),
+ node.at("/0/client_type").asText());
+ assertFalse(node.at("/0/permitted").asBoolean());
+ assertFalse(node.at("/0/registration_date").isMissingNode());
+ assertFalse(node.at("/0/source").isMissingNode());
+ assertEquals(refreshTokenExpiry,
+ node.at("/0/refresh_token_expiry").asInt());
+ }
+
+ @Test
+ public void testListPluginsUnauthorizedPublic ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("super_client_id", publicClientId);
+ testListPluginsClientUnauthorized(form);
+ }
+
+ @Test
+ public void testListPluginsUnauthorizedConfidential ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("super_client_id", confidentialClientId2);
+ form.param("super_client_secret", clientSecret);
+ testListPluginsClientUnauthorized(form);
+ }
+
+ @Test
+ public void testListPluginsMissingClientSecret ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("super_client_id", confidentialClientId);
+ Response response = target().path(API_VERSION).path("plugins").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertFalse(node.at("/error_description").isMissingNode());
+ }
+
+ private void testListPluginsClientUnauthorized (Form form)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("plugins").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ assertEquals(OAuth2Error.UNAUTHORIZED_CLIENT,
+ node.at("/error").asText());
+ assertFalse(node.at("/error_description").isMissingNode());
+ }
+
+ @Test
+ public void testListPluginsUserUnauthorized ()
+ throws ProcessingException, KustvaktException {
+ Form form = getSuperClientForm();
+ Response response = target().path(API_VERSION).path("plugins").request()
+ .header(Attributes.AUTHORIZATION, "Bearer blahblah")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.INVALID_ACCESS_TOKEN,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testListPluginsConcurrent () throws InterruptedException {
+ ExecutorService executorService = Executors.newFixedThreadPool(3);
+ List<Future<Void>> futures = new ArrayList<>();
+ for (int i = 0; i < 3; i++) {
+ futures.add(executorService
+ .submit(new PluginListCallable("Thread " + (i + 1))));
+ }
+ executorService.shutdown();
+ executorService.awaitTermination(2, TimeUnit.SECONDS);
+ for (Future<Void> future : futures) {
+ try {
+ // This will re-throw any exceptions
+ future.get();
+ // that occurred in threads
+ }
+ catch (ExecutionException e) {
+ fail("Test failed: " + e.getCause().getMessage());
+ }
+ }
+ }
+
+ class PluginListCallable implements Callable<Void> {
+
+ private final String name;
+
+ public PluginListCallable (String name) {
+ this.name = name;
+ }
+
+ @Override
+ public Void call () {
+ Form form = getSuperClientForm();
+ try {
+ Response response = target().path(API_VERSION).path("plugins")
+ .request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.size());
+ }
+ catch (KustvaktException e) {
+ e.printStackTrace();
+ throw new RuntimeException(name, e);
+ }
+ return null;
+ }
+ }
+
+ @Test
+ public void testListAllPlugins ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = listPlugins(false);
+ assertEquals(2, node.size());
+ assertFalse(node.at("/0/client_id").isMissingNode());
+ assertFalse(node.at("/0/client_name").isMissingNode());
+ assertFalse(node.at("/0/client_description").isMissingNode());
+ assertFalse(node.at("/0/client_type").isMissingNode());
+ assertFalse(node.at("/0/permitted").isMissingNode());
+ assertFalse(node.at("/0/registration_date").isMissingNode());
+ assertFalse(node.at("/0/source").isMissingNode());
+ assertFalse(node.at("/0/refresh_token_expiry").isMissingNode());
+ // assertTrue(node.at("/1/refresh_token_expiry").isMissingNode());
+ }
+
+ private JsonNode listPlugins (boolean permitted_only)
+ throws ProcessingException, KustvaktException {
+ Form form = getSuperClientForm();
+ if (permitted_only) {
+ form.param("permitted_only", Boolean.toString(permitted_only));
+ }
+ Response response = target().path(API_VERSION).path("plugins").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+
+ private void testInstallConfidentialPlugin (String superClientId,
+ String clientId, String username)
+ throws ProcessingException, KustvaktException {
+ Form form = getSuperClientForm();
+ form.param("client_id", clientId);
+ Response response = installPlugin(form);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(clientId, node.at("/client_id").asText());
+ assertEquals(superClientId, node.at("/super_client_id").asText());
+ assertFalse(node.at("/name").isMissingNode());
+ assertFalse(node.at("/description").isMissingNode());
+ assertFalse(node.at("/url").isMissingNode());
+ assertFalse(node.at("/installed_date").isMissingNode());
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ testRetrieveInstalledPlugin(superClientId, clientId, username);
+ }
+
+ @Test
+ public void testInstallPublicPlugin ()
+ throws ProcessingException, KustvaktException {
+ Form form = getSuperClientForm();
+ form.param("client_id", publicClientId2);
+ Response response = installPlugin(form);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(publicClientId2, node.at("/client_id").asText());
+ assertEquals(superClientId, node.at("/super_client_id").asText());
+ assertFalse(node.at("/name").isMissingNode());
+ assertFalse(node.at("/description").isMissingNode());
+ assertFalse(node.at("/url").isMissingNode());
+ assertFalse(node.at("/installed_date").isMissingNode());
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ testInstallPluginRedundant(form);
+ testRetrieveInstalledPlugin(superClientId, publicClientId2, username);
+ response = uninstallPlugin(publicClientId2, username);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = retrieveUserInstalledPlugin(getSuperClientForm());
+ assertTrue(node.isEmpty());
+ }
+
+ private void testInstallPluginRedundant (Form form)
+ throws ProcessingException, KustvaktException {
+ Response response = installPlugin(form);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.PLUGIN_HAS_BEEN_INSTALLED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ private void testInstallPluginNotPermitted (String clientId)
+ throws ProcessingException, KustvaktException {
+ Form form = getSuperClientForm();
+ form.param("client_id", clientId);
+ Response response = installPlugin(form);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.PLUGIN_NOT_PERMITTED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testInstallPluginMissingClientId ()
+ throws ProcessingException, KustvaktException {
+ Form form = getSuperClientForm();
+ Response response = installPlugin(form);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testInstallPluginInvalidClientId ()
+ throws ProcessingException, KustvaktException {
+ Form form = getSuperClientForm();
+ form.param("client_id", "unknown");
+ Response response = installPlugin(form);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/error_description").asText(),
+ "Unknown client: unknown");
+ assertEquals(node.at("/error").asText(), "invalid_client");
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testInstallPluginMissingSuperClientSecret ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("super_client_id", superClientId);
+ Response response = installPlugin(form);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/error_description").asText(),
+ "Missing parameter: super_client_secret");
+ assertEquals(node.at("/error").asText(), "invalid_request");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testInstallPluginMissingSuperClientId ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ Response response = installPlugin(form);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/error_description").asText(),
+ "Missing parameter: super_client_id");
+ assertEquals(node.at("/error").asText(), "invalid_request");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testInstallPluginUnauthorizedClient ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("super_client_id", confidentialClientId);
+ form.param("super_client_secret", clientSecret);
+ Response response = installPlugin(form);
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/error").asText(), "unauthorized_client");
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ }
+
+ private Response installPlugin (Form form)
+ throws ProcessingException, KustvaktException {
+ return target().path(API_VERSION).path("plugins").path("install")
+ .request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ }
+
+ private Response uninstallPlugin (String clientId, String username)
+ throws ProcessingException, KustvaktException {
+ Form form = getSuperClientForm();
+ form.param("client_id", clientId);
+ return target().path(API_VERSION).path("plugins").path("uninstall")
+ .request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ }
+
+ private void testRetrieveInstalledPlugin (String superClientId,
+ String clientId, String installedBy) throws KustvaktException {
+ InstalledPlugin plugin = pluginDao
+ .retrieveInstalledPlugin(superClientId, clientId, installedBy);
+ assertEquals(clientId, plugin.getClient().getId());
+ assertEquals(superClientId, plugin.getSuperClient().getId());
+ assertEquals(installedBy, plugin.getInstalledBy());
+ assertTrue(plugin.getId() > 0);
+ assertTrue(plugin.getInstalledDate() != null);
+ }
+
+ @Test
+ public void testListUserInstalledPlugins ()
+ throws ProcessingException, KustvaktException, IOException {
+ testInstallConfidentialPlugin(superClientId, confidentialClientId,
+ username);
+ JsonNode node = testRequestAccessToken(confidentialClientId);
+ String accessToken = node.at("/access_token").asText();
+ String refreshToken = node.at("/refresh_token").asText();
+ testSearchWithOAuth2Token(accessToken);
+ testInstallConfidentialPlugin(superClientId, confidentialClientId2,
+ username);
+ node = retrieveUserInstalledPlugin(getSuperClientForm());
+ assertEquals(2, node.size());
+ Response response = uninstallPlugin(confidentialClientId, username);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = retrieveUserInstalledPlugin(getSuperClientForm());
+ assertEquals(1, node.size());
+ testRequestTokenWithRevokedRefreshToken(confidentialClientId,
+ clientSecret, refreshToken);
+ testSearchWithRevokedAccessToken(accessToken);
+ response = uninstallPlugin(confidentialClientId2, username);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = retrieveUserInstalledPlugin(getSuperClientForm());
+ assertEquals(0, node.size());
+ testReinstallUninstalledPlugin();
+ testUninstallNotInstalledPlugin();
+ }
+
+ private void testReinstallUninstalledPlugin ()
+ throws ProcessingException, KustvaktException {
+ testInstallConfidentialPlugin(superClientId, confidentialClientId2,
+ username);
+ JsonNode node = retrieveUserInstalledPlugin(getSuperClientForm());
+ assertEquals(1, node.size());
+ Response response = uninstallPlugin(confidentialClientId2, username);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = retrieveUserInstalledPlugin(getSuperClientForm());
+ assertEquals(0, node.size());
+ }
+
+ private JsonNode testRequestAccessToken (String clientId)
+ throws KustvaktException {
+ String userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "password");
+ String code = requestAuthorizationCode(clientId, userAuthHeader);
+ Response response = requestTokenWithAuthorizationCodeAndForm(clientId,
+ clientSecret, code);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ return node;
+ }
+
+ private void testUninstallNotInstalledPlugin ()
+ throws ProcessingException, KustvaktException {
+ Response response = uninstallPlugin(confidentialClientId2, username);
+ assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ }
+
+ private JsonNode retrieveUserInstalledPlugin (Form form)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("plugins")
+ .path("installed").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2RClientTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2RClientTest.java
new file mode 100644
index 0000000..d0eab61
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2RClientTest.java
@@ -0,0 +1,83 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+import java.net.URI;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
+
+public class OAuth2RClientTest extends OAuth2TestBase {
+
+ private String username = "OAuth2ClientControllerTest";
+
+ private String userAuthHeader;
+
+ public OAuth2RClientTest () throws KustvaktException {
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("R-user", "password");
+ }
+
+ public OAuth2ClientJson createOAuth2RClient () {
+ OAuth2ClientJson client = new OAuth2ClientJson();
+ client.setName("R client");
+ client.setType(OAuth2ClientType.PUBLIC);
+ client.setDescription("An R client with httr web server.");
+ client.setRedirectURI("http://localhost:1410");
+ return client;
+ }
+
+ @Test
+ public void testRClientWithLocalhost ()
+ throws ProcessingException, KustvaktException, IOException {
+ // Register client
+ OAuth2ClientJson clientJson = createOAuth2RClient();
+ Response response = registerClient(username, clientJson);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String clientId = node.at("/client_id").asText();
+ // send authorization
+ String code = testAuthorize(clientId);
+ // send token request
+ response = requestTokenWithAuthorizationCodeAndForm(clientId, null,
+ code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ // testing
+ String accessToken = node.at("/access_token").asText();
+ testSearchWithOAuth2Token(accessToken);
+ // cleaning up
+ deregisterClient(username, clientId);
+ testSearchWithRevokedAccessToken(accessToken);
+ }
+
+ private String testAuthorize (String clientId) throws KustvaktException {
+ Response response = requestAuthorizationCode("code", clientId, "",
+ "search", "", userAuthHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+ URI redirectUri = response.getLocation();
+ assertEquals(redirectUri.getScheme(), "http");
+ assertEquals(redirectUri.getHost(), "localhost");
+ assertEquals(1410, redirectUri.getPort());
+ MultiValueMap<String, String> params = UriComponentsBuilder
+ .fromUri(redirectUri).build().getQueryParams();
+ String code = params.getFirst("code");
+ assertNotNull(code);
+ return code;
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
new file mode 100644
index 0000000..b27fd0f
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
@@ -0,0 +1,487 @@
+package de.ids_mannheim.korap.web.controller;
+
+import java.io.IOException;
+import java.net.URI;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.http.entity.ContentType;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.uri.UriComponent;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import com.nimbusds.oauth2.sdk.GrantType;
+import com.nimbusds.oauth2.sdk.OAuth2Error;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.constant.OAuth2Scope;
+import de.ids_mannheim.korap.encryption.RandomCodeGenerator;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
+import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
+import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+import de.ids_mannheim.korap.oauth2.entity.AccessToken;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.Invocation.Builder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Provides common methods and variables for OAuth2 tests,
+ * and does not run any test.
+ *
+ * @author margaretha
+ *
+ */
+public abstract class OAuth2TestBase extends SpringJerseyTest {
+
+ @Autowired
+ private AccessTokenDao tokenDao;
+ @Autowired
+ private OAuth2ClientDao clientDao;
+ @Autowired
+ private RandomCodeGenerator codeGenerator;
+ @Autowired
+ protected RefreshTokenDao refreshTokenDao;
+
+ protected String publicClientId = "8bIDtZnH6NvRkW2Fq";
+ // without registered redirect URI
+ protected String publicClientId2 = "nW5qM63Rb2a7KdT9L";
+ protected String confidentialClientId = "9aHsGW6QflV13ixNpez";
+ protected String confidentialClientId2 = "52atrL0ajex_3_5imd9Mgw";
+ protected String superClientId = "fCBbQkAyYzI4NzUxMg";
+ protected String clientSecret = "secret";
+ protected String state = "thisIsMyState";
+
+ public static String ACCESS_TOKEN_TYPE = "access_token";
+ public static String REFRESH_TOKEN_TYPE = "refresh_token";
+
+ protected int defaultRefreshTokenExpiry = TimeUtils
+ .convertTimeToSeconds("365D");
+
+ protected String clientURL = "http://example.client.com";
+ protected String clientRedirectUri = "https://example.client.com/redirect";
+
+ protected MultivaluedMap<String, String> getQueryParamsFromURI (URI uri) {
+ return UriComponent.decodeQuery(uri, true);
+ };
+
+ protected Form getSuperClientForm () {
+ Form form = new Form();
+ form.param("super_client_id", superClientId);
+ form.param("super_client_secret", clientSecret);
+ return form;
+ }
+
+ protected String parseAuthorizationCode (Response response) {
+
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+
+ URI redirectUri = response.getLocation();
+ MultiValueMap<String, String> params = UriComponentsBuilder
+ .fromUri(redirectUri).build().getQueryParams();
+ return params.getFirst("code");
+ }
+
+ protected Response requestAuthorizationCode (String responseType,
+ String clientId, String redirectUri, String scope, String state,
+ String authHeader) throws KustvaktException {
+
+ ClientConfig clientConfig = new ClientConfig();
+ clientConfig.property(ClientProperties.FOLLOW_REDIRECTS, false);
+ Client client = ClientBuilder.newClient(clientConfig);
+
+ WebTarget request = client.target(getBaseUri()).path(API_VERSION)
+ .path("oauth2").path("authorize");
+
+ if (!responseType.isEmpty()) {
+ request = request.queryParam("response_type", responseType);
+ }
+ if (!clientId.isEmpty()) {
+ request = request.queryParam("client_id", clientId);
+ }
+ if (!redirectUri.isEmpty()) {
+ request = request.queryParam("redirect_uri", redirectUri);
+ }
+ if (!scope.isEmpty()) {
+ request = request.queryParam("scope", scope);
+ }
+ if (!state.isEmpty()) {
+ request = request.queryParam("state", state);
+ }
+
+ Builder builder = request.request().header(Attributes.AUTHORIZATION,
+ authHeader);
+
+ return builder.get();
+ }
+
+ protected String requestAuthorizationCode (String clientId,
+ String authHeader) throws KustvaktException {
+
+ Response response = requestAuthorizationCode("code", clientId, "",
+ "search match_info", "", authHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+ URI redirectUri = response.getLocation();
+
+ MultiValueMap<String, String> params = UriComponentsBuilder
+ .fromUri(redirectUri).build().getQueryParams();
+ return params.getFirst("code");
+ }
+
+ protected String requestAuthorizationCode (String clientId,
+ String redirect_uri, String authHeader) throws KustvaktException {
+ Response response = requestAuthorizationCode("code", clientId,
+ redirect_uri, "search", "", authHeader);
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+ URI redirectUri = response.getLocation();
+
+ MultiValueMap<String, String> params = UriComponentsBuilder
+ .fromUri(redirectUri).build().getQueryParams();
+ return params.getFirst("code");
+ }
+
+ protected Response requestToken (Form form) throws KustvaktException {
+ return target().path(API_VERSION).path("oauth2").path("token").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ }
+
+ // client credentials as form params
+ protected Response requestTokenWithAuthorizationCodeAndForm (
+ String clientId, String clientSecret, String code)
+ throws KustvaktException {
+
+ Form form = new Form();
+ form.param("grant_type", "authorization_code");
+ form.param("client_id", clientId);
+ form.param("client_secret", clientSecret);
+ form.param("code", code);
+
+ return target().path(API_VERSION).path("oauth2").path("token").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ }
+
+ protected Response requestTokenWithAuthorizationCodeAndForm (
+ String clientId, String clientSecret, String code,
+ String redirectUri) throws KustvaktException {
+
+ Form form = new Form();
+ form.param("grant_type", "authorization_code");
+ form.param("client_id", clientId);
+ form.param("client_secret", clientSecret);
+ form.param("code", code);
+ if (redirectUri != null) {
+ form.param("redirect_uri", redirectUri);
+ }
+
+ return target().path(API_VERSION).path("oauth2").path("token").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ }
+
+ // client credentials in authorization header
+ protected JsonNode requestTokenWithAuthorizationCodeAndHeader (
+ String clientId, String code, String authHeader)
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", "authorization_code");
+ form.param("client_id", clientId);
+ form.param("code", code);
+
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+
+ protected Response requestTokenWithDoryPassword (String clientId,
+ String clientSecret) throws KustvaktException {
+ return requestTokenWithPassword(clientId, clientSecret, "dory",
+ "password");
+ }
+
+ protected Response requestTokenWithPassword (String clientId,
+ String clientSecret, String username, String password)
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", "password");
+ form.param("client_id", clientId);
+ if (clientSecret != null && !clientSecret.isEmpty()) {
+ form.param("client_secret", clientSecret);
+ }
+ form.param("username", username);
+ form.param("password", password);
+
+ return requestToken(form);
+ }
+
+ protected void testRequestTokenWithRevokedRefreshToken (String clientId,
+ String clientSecret, String refreshToken) throws KustvaktException {
+ Form form = new Form();
+ form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+ form.param("client_id", clientId);
+ form.param("client_secret", clientSecret);
+ form.param("refresh_token", refreshToken);
+ if (clientSecret != null) {
+ form.param("client_secret", clientSecret);
+ }
+
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_GRANT.getCode(),
+ node.at("/error").asText());
+ assertEquals("Refresh token has been revoked",
+ node.at("/error_description").asText());
+ }
+
+ protected Response registerClient (String username, OAuth2ClientJson json)
+ throws ProcessingException, KustvaktException {
+ return target().path(API_VERSION).path("oauth2").path("client")
+ .path("register").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username,
+ "password"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .post(Entity.json(json));
+ }
+
+ protected Response registerConfidentialClient (String username)
+ throws KustvaktException {
+
+ OAuth2ClientJson json = new OAuth2ClientJson();
+ json.setName("OAuth2ClientTest");
+ json.setType(OAuth2ClientType.CONFIDENTIAL);
+ json.setUrl(clientURL);
+ json.setRedirectURI(clientRedirectUri);
+ json.setDescription("This is a confidential test client.");
+
+ return registerClient(username, json);
+ }
+
+ protected void testConfidentialClientInfo (String clientId, String username)
+ throws ProcessingException, KustvaktException {
+ JsonNode clientInfo = retrieveClientInfo(clientId, username);
+ assertEquals(clientId, clientInfo.at("/client_id").asText());
+ assertEquals("OAuth2ClientTest",
+ clientInfo.at("/client_name").asText());
+ assertEquals(OAuth2ClientType.CONFIDENTIAL.name(),
+ clientInfo.at("/client_type").asText());
+ assertEquals(username, clientInfo.at("/registered_by").asText());
+ assertEquals(clientURL, clientInfo.at("/client_url").asText());
+ assertEquals(clientRedirectUri,
+ clientInfo.at("/client_redirect_uri").asText());
+ // 31536000 seconds
+ assertEquals(defaultRefreshTokenExpiry,
+ clientInfo.at("/refresh_token_expiry").asInt());
+ assertNotNull(clientInfo.at("/description"));
+ assertNotNull(clientInfo.at("/registration_date"));
+ assertTrue(clientInfo.at("/permitted").asBoolean());
+ assertTrue(clientInfo.at("/source").isMissingNode());
+
+ }
+
+ protected void deregisterClient (String username, String clientId)
+ throws ProcessingException, KustvaktException {
+
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("deregister").path(clientId).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .delete();
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ protected JsonNode retrieveClientInfo (String clientId, String username)
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("super_client_id", superClientId);
+ form.param("super_client_secret", clientSecret);
+
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path(clientId).request()
+ // .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ // .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+
+ protected Response searchWithAccessToken (String accessToken) {
+ return target().path(API_VERSION).path("search")
+ .queryParam("q", "Wasser").queryParam("ql", "poliqarp")
+ .request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ }
+
+ protected void testSearchWithOAuth2Token (String accessToken)
+ throws KustvaktException, IOException {
+ Response response = searchWithAccessToken(accessToken);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node);
+ assertEquals(25, node.at("/matches").size());
+ }
+
+ protected void testSearchWithRevokedAccessToken (String accessToken)
+ throws KustvaktException {
+ Response response = searchWithAccessToken(accessToken);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ACCESS_TOKEN,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Access token is invalid",
+ node.at("/errors/0/1").asText());
+ }
+
+ protected void testRevokeTokenViaSuperClient (String token,
+ String userAuthHeader) {
+ Form form = new Form();
+ form.param("token", token);
+ form.param("super_client_id", superClientId);
+ form.param("super_client_secret", clientSecret);
+
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("revoke").path("super").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .header(Attributes.AUTHORIZATION, userAuthHeader)
+ .post(Entity.form(form));
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ assertEquals("SUCCESS", response.readEntity(String.class));
+ }
+
+ protected void testRevokeToken (String token, String clientId,
+ String clientSecret, String tokenType) {
+ Form form = new Form();
+ form.param("token_type", tokenType);
+ form.param("token", token);
+ form.param("client_id", clientId);
+ if (clientSecret != null) {
+ form.param("client_secret", clientSecret);
+ }
+
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("revoke").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ assertEquals("SUCCESS", response.readEntity(String.class));
+ }
+
+ protected JsonNode listUserRegisteredClients (String username)
+ throws ProcessingException, KustvaktException {
+ Form form = getSuperClientForm();
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("client").path("list").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pwd"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+
+ protected void testInvalidRedirectUri (String entity, String contentType,
+ boolean includeState, int status) throws KustvaktException {
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST.getCode(),
+ node.at("/error").asText());
+ assertEquals("Invalid redirect URI",
+ node.at("/error_description").asText());
+ if (includeState) {
+ assertEquals(state, node.at("/state").asText());
+ }
+
+ assertEquals("application/json;charset=utf-8", contentType);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), status);
+ }
+
+ protected String createExpiredAccessToken () throws KustvaktException {
+ String authToken = codeGenerator.createRandomCode();
+
+ // create new access token
+ OAuth2Client client = clientDao.retrieveClientById(publicClientId);
+
+ ZonedDateTime now = ZonedDateTime
+ .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE));
+ Set<AccessScope> scopes = new HashSet<>();
+ scopes.add(new AccessScope(OAuth2Scope.EDIT_VC));
+
+ AccessToken accessToken = new AccessToken();
+ accessToken.setCreatedDate(now.minusSeconds(5));
+ accessToken.setExpiryDate(now.minusSeconds(3));
+ accessToken.setToken(authToken);
+ accessToken.setScopes(scopes);
+ accessToken.setUserId("marlin");
+ accessToken.setClient(client);
+ accessToken.setUserAuthenticationTime(now.minusSeconds(5));
+ tokenDao.storeAccessToken(accessToken);
+ return authToken;
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/QueryReferenceControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/QueryReferenceControllerTest.java
new file mode 100644
index 0000000..57dc847
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/QueryReferenceControllerTest.java
@@ -0,0 +1,334 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.http.entity.ContentType;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+import jakarta.ws.rs.client.Entity;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.constant.ResourceType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.user.User.CorpusAccess;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class QueryReferenceControllerTest extends SpringJerseyTest {
+
+ private String testUser = "qRefControllerTest";
+
+ private String adminUser = "admin";
+
+ private String system = "system";
+
+ private void testRetrieveQueryByName (String qName, String query,
+ String queryCreator, String username, ResourceType resourceType,
+ CorpusAccess access) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("query")
+ .path("~" + queryCreator).path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .get();
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(qName, node.at("/name").asText());
+ assertEquals(resourceType.displayName(), node.at("/type").asText());
+ assertEquals(queryCreator, node.at("/createdBy").asText());
+ assertEquals(query, node.at("/query").asText());
+ assertEquals(node.at("/queryLanguage").asText(), "poliqarp");
+ assertEquals(access.name(), node.at("/requiredAccess").asText());
+ }
+
+ private void testUpdateQuery (String qName, String qCreator,
+ String username, ResourceType type)
+ throws ProcessingException, KustvaktException {
+ String json = "{\"query\": \"Sonne\""
+ + ",\"queryLanguage\": \"poliqarp\"}";
+ Response response = target().path(API_VERSION).path("query")
+ .path("~" + qCreator).path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
+ testRetrieveQueryByName(qName, "Sonne", qCreator, username, type,
+ CorpusAccess.PUB);
+ }
+
+ @Test
+ public void testCreatePrivateQuery () throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\"" + ",\"queryType\": \"QUERY\""
+ + ",\"queryLanguage\": \"poliqarp\"" + ",\"query\": \"der\"}";
+ String qName = "new_query";
+ Response response = target().path(API_VERSION).path("query")
+ .path("~" + testUser).path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ testRetrieveQueryByName(qName, "der", testUser, testUser,
+ ResourceType.PRIVATE, CorpusAccess.PUB);
+ testUpdateQuery(qName, testUser, testUser, ResourceType.PRIVATE);
+ testDeleteQueryByName(qName, testUser, testUser);
+ }
+
+ @Test
+ public void testCreatePublishQuery () throws KustvaktException {
+ String json = "{\"type\": \"PUBLISHED\"" + ",\"queryType\": \"QUERY\""
+ + ",\"queryLanguage\": \"poliqarp\"" + ",\"query\": \"Regen\"}";
+ String qName = "publish_query";
+ Response response = target().path(API_VERSION).path("query")
+ .path("~" + testUser).path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ testRetrieveQueryByName(qName, "Regen", testUser, testUser,
+ ResourceType.PUBLISHED, CorpusAccess.PUB);
+ testDeleteQueryByName(qName, testUser, testUser);
+ // check if hidden group has been created
+ }
+
+ @Test
+ public void testCreateUserQueryByAdmin () throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\"" + ",\"queryType\": \"QUERY\""
+ + ",\"queryLanguage\": \"poliqarp\""
+ + ",\"query\": \"Sommer\"}";
+ String qName = "marlin-query";
+ Response response = target().path(API_VERSION).path("query")
+ .path("~marlin").path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(adminUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ testRetrieveQueryByName(qName, "Sommer", "marlin", adminUser,
+ ResourceType.PRIVATE, CorpusAccess.PUB);
+ testUpdateQuery(qName, "marlin", adminUser, ResourceType.PRIVATE);
+ testDeleteQueryByName(qName, "marlin", adminUser);
+ }
+
+ @Test
+ public void testCreateSystemQuery () throws KustvaktException {
+ String json = "{\"type\": \"SYSTEM\"" + ",\"queryType\": \"QUERY\""
+ + ",\"queryLanguage\": \"poliqarp\""
+ + ",\"query\": \"Sommer\"}";
+ String qName = "system-query";
+ Response response = target().path(API_VERSION).path("query")
+ .path("~system").path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(adminUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ testRetrieveQueryByName(qName, "Sommer", system, adminUser,
+ ResourceType.SYSTEM, CorpusAccess.PUB);
+ testUpdateQuery(qName, system, adminUser, ResourceType.SYSTEM);
+ testDeleteSystemQueryUnauthorized(qName);
+ testDeleteQueryByName(qName, system, adminUser);
+ }
+
+ @Test
+ public void testCreateSystemQueryUnauthorized () throws KustvaktException {
+ String json = "{\"type\": \"SYSTEM\"" + ",\"queryType\": \"QUERY\""
+ + ",\"queryLanguage\": \"poliqarp\""
+ + ",\"query\": \"Sommer\"}";
+ Response response = target().path(API_VERSION).path("query")
+ .path("~" + testUser).path("system-query").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Unauthorized operation for user: " + testUser,
+ node.at("/errors/0/1").asText());
+ }
+
+ @Test
+ public void testCreateQueryMissingQueryType () throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\""
+ + ",\"queryLanguage\": \"poliqarp\"" + ",\"query\": \"Sohn\"}";
+ String qName = "new_query";
+ Response response = target().path(API_VERSION).path("query")
+ .path("~" + testUser).path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ testRetrieveQueryByName(qName, "Sohn", testUser, testUser,
+ ResourceType.PRIVATE, CorpusAccess.PUB);
+ testDeleteQueryByName(qName, testUser, testUser);
+ }
+
+ @Test
+ public void testCreateQueryMissingQueryLanguage ()
+ throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\"" + ",\"queryType\": \"QUERY\""
+ + ",\"query\": \"Sohn\"}";
+ String qName = "new_query";
+ Response response = target().path(API_VERSION).path("query")
+ .path("~" + testUser).path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(), "queryLanguage is null");
+ assertEquals(node.at("/errors/0/2").asText(), "queryLanguage");
+ }
+
+ @Test
+ public void testCreateQueryMissingQuery () throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\"" + ",\"queryType\": \"QUERY\""
+ + ",\"queryLanguage\": \"poliqarp\"}";
+ String qName = "new_query";
+ Response response = target().path(API_VERSION).path("query")
+ .path("~" + testUser).path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(), "query is null");
+ assertEquals(node.at("/errors/0/2").asText(), "query");
+ }
+
+ @Test
+ public void testCreateQueryMissingResourceType () throws KustvaktException {
+ String json = "{\"query\": \"Wind\""
+ + ",\"queryLanguage\": \"poliqarp\"}";
+ String qName = "new_query";
+ Response response = target().path(API_VERSION).path("query")
+ .path("~" + testUser).path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(), "type is null");
+ assertEquals(node.at("/errors/0/2").asText(), "type");
+ }
+
+ private void testDeleteQueryByName (String qName, String qCreator,
+ String username) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("query")
+ .path("~" + qCreator).path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testDeleteQueryUnauthorized () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("query")
+ .path("~dory").path("dory-q").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .delete();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Unauthorized operation for user: " + testUser,
+ node.at("/errors/0/1").asText());
+ }
+
+ private void testDeleteSystemQueryUnauthorized (String qName)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("query")
+ .path("~system").path(qName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .delete();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Unauthorized operation for user: " + testUser,
+ node.at("/errors/0/1").asText());
+ }
+
+ @Test
+ public void testDeleteNonExistingQuery () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("query")
+ .path("~dory").path("non-existing-query").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .delete();
+ assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Query dory/non-existing-query is not found.");
+ assertEquals(node.at("/errors/0/2").asText(),
+ "dory/non-existing-query");
+ }
+
+ @Test
+ public void testListAvailableQueryForDory ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = testListAvailableQuery("dory");
+ assertEquals(2, node.size());
+ }
+
+ @Test
+ public void testListAvailableQueryForPearl ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = testListAvailableQuery("pearl");
+ assertEquals(1, node.size());
+ assertEquals(node.at("/0/name").asText(), "system-q");
+ assertEquals(ResourceType.SYSTEM.displayName(),
+ node.at("/0/type").asText());
+ assertEquals(node.at("/0/description").asText(), "\"system\" query");
+ assertEquals(node.at("/0/query").asText(), "[]");
+ assertEquals(CorpusAccess.FREE.name(),
+ node.at("/0/requiredAccess").asText());
+ // assertEquals("koral:token", node.at("/0/koralQuery/@type").asText());
+ }
+
+ private JsonNode testListAvailableQuery (String username)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("query").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ return node;
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/QueryReferenceSearchTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/QueryReferenceSearchTest.java
new file mode 100644
index 0000000..27f3567
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/QueryReferenceSearchTest.java
@@ -0,0 +1,37 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.ws.rs.core.Response;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/*@Test
+ public void testSearchWithVCRefEqual () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"dory/dory-q\"")
+ .get();
+
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertTrue(node.at("/matches").size() > 0);
+ }
+*/
+public class QueryReferenceSearchTest {
+ /*@Test
+ public void testSearchWithVCRefEqual () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"dory/dory-q\"")
+ .get();
+
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertTrue(node.at("/matches").size() > 0);
+ }
+ */
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/QuerySerializationControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/QuerySerializationControllerTest.java
new file mode 100644
index 0000000..5c70f57
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/QuerySerializationControllerTest.java
@@ -0,0 +1,228 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Iterator;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+// EM: The API is disabled
+@Disabled
+public class QuerySerializationControllerTest extends SpringJerseyTest {
+
+ @Test
+ public void testQuerySerializationFilteredPublic ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("corpus/WPD13/query").queryParam("q", "[orth=der]")
+ .queryParam("ql", "poliqarp").queryParam("context", "base/s:s")
+ .request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(node.at("/collection/key").asText(), "corpusSigle");
+ assertEquals(node.at("/collection/value").asText(), "WPD13");
+ }
+
+ @Test
+ public void testQuerySerializationUnexistingResource ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("corpus/ZUW19/query").queryParam("q", "[orth=der]")
+ .queryParam("ql", "poliqarp").queryParam("context", "base/s:s")
+ .request().method("GET");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(101, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/2").asText(),
+ "[Cannot found public Corpus with ids: [ZUW19]]");
+ }
+
+ @Test
+ public void testQuerySerializationWithNonPublicCorpus ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("corpus/BRZ10/query").queryParam("q", "[orth=der]")
+ .queryParam("ql", "poliqarp").queryParam("context", "base/s:s")
+ .request().method("GET");
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(101, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/2").asText(),
+ "[Cannot found public Corpus with ids: [BRZ10]]");
+ }
+
+ @Test
+ public void testQuerySerializationWithAuthentication ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("corpus/BRZ10/query").queryParam("q", "[orth=der]")
+ .queryParam("ql", "poliqarp").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:doc");
+ assertEquals(node.at("/collection/key").asText(), "corpusSigle");
+ assertEquals(node.at("/collection/value").asText(), "BRZ10");
+ }
+
+ @Test
+ public void testQuerySerializationWithNewCollection ()
+ throws KustvaktException {
+ // Add Virtual Collection
+ Response response = target().path(API_VERSION).path("virtualcollection")
+ .queryParam("filter", "false")
+ .queryParam("query",
+ "creationDate since 1775 & corpusSigle=GOE")
+ .queryParam("name", "Weimarer Werke")
+ .queryParam("description", "Goethe-Werke in Weimar (seit 1775)")
+ .request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .post(Entity.json(""));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertTrue(node.isObject());
+ assertEquals(node.path("name").asText(), "Weimarer Werke");
+ // Get virtual collections
+ response = target().path(API_VERSION).path("collection").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ ent = response.readEntity(String.class);
+ node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ Iterator<JsonNode> it = node.elements();
+ String id = null;
+ while (it.hasNext()) {
+ JsonNode next = (JsonNode) it.next();
+ if ("Weimarer Werke".equals(next.path("name").asText()))
+ id = next.path("id").asText();
+ }
+ assertNotNull(id);
+ assertFalse(id.isEmpty());
+ // query serialization service
+ response = target().path(API_VERSION).path("collection").path(id)
+ .path("query").queryParam("q", "[orth=der]")
+ .queryParam("ql", "poliqarp").queryParam("context", "base/s:s")
+ .request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ ent = response.readEntity(String.class);
+ node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ // System.out.println("NODE " + ent);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operands/0/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/0/key").asText(),
+ "creationDate");
+ assertEquals(node.at("/collection/operands/0/value").asText(), "1775");
+ assertEquals(node.at("/collection/operands/0/type").asText(),
+ "type:date");
+ assertEquals(node.at("/collection/operands/0/match").asText(),
+ "match:geq");
+ assertEquals(node.at("/collection/operands/1/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/1/key").asText(),
+ "corpusSigle");
+ assertEquals(node.at("/collection/operands/1/value").asText(), "GOE");
+ assertEquals(node.at("/collection/operands/1/match").asText(),
+ "match:eq");
+ }
+
+ @Test
+ public void testQuerySerializationOfVirtualCollection ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("collection/GOE-VC/query").queryParam("q", "[orth=der]")
+ .queryParam("ql", "poliqarp").queryParam("context", "base/s:s")
+ .request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(node.at("/collection/operands/0/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/0/key").asText(),
+ "corpusSigle");
+ assertEquals(node.at("/collection/operands/0/value").asText(), "GOE");
+ assertEquals(node.at("/collection/operands/1/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/1/key").asText(),
+ "creationDate");
+ assertEquals(node.at("/collection/operands/1/value").asText(),
+ "1810-01-01");
+ }
+
+ @Test
+ public void testMetaQuerySerialization () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("query")
+ .queryParam("context", "sentence").queryParam("count", "20")
+ .queryParam("page", "5").queryParam("cutoff", "true")
+ .queryParam("q", "[pos=ADJA]").queryParam("ql", "poliqarp")
+ .request().method("GET");
+ assertEquals(response.getStatus(), Status.OK.getStatusCode());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/meta/context").asText(), "sentence");
+ assertEquals(20, node.at("/meta/count").asInt());
+ assertEquals(5, node.at("/meta/startPage").asInt());
+ assertEquals(true, node.at("/meta/cutOff").asBoolean());
+ assertEquals(node.at("/query/wrap/@type").asText(), "koral:term");
+ assertEquals(node.at("/query/wrap/layer").asText(), "pos");
+ assertEquals(node.at("/query/wrap/match").asText(), "match:eq");
+ assertEquals(node.at("/query/wrap/key").asText(), "ADJA");
+ }
+
+ @Test
+ public void testMetaQuerySerializationWithOffset ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("query")
+ .queryParam("context", "sentence").queryParam("count", "20")
+ .queryParam("page", "5").queryParam("offset", "2")
+ .queryParam("cutoff", "true").queryParam("q", "[pos=ADJA]")
+ .queryParam("ql", "poliqarp").request().method("GET");
+ assertEquals(response.getStatus(), Status.OK.getStatusCode());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/meta/context").asText(), "sentence");
+ assertEquals(20, node.at("/meta/count").asInt());
+ assertEquals(2, node.at("/meta/startIndex").asInt());
+ assertEquals(true, node.at("/meta/cutOff").asBoolean());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/ResourceInfoControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/ResourceInfoControllerTest.java
new file mode 100644
index 0000000..d2d4fed
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/ResourceInfoControllerTest.java
@@ -0,0 +1,176 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author hanl, margaretha
+ * @lastUpdate 19/04/2017
+ * EM: FIX ME: Database restructure
+ */
+@Disabled
+public class ResourceInfoControllerTest extends SpringJerseyTest {
+
+ @Test
+ public void testGetPublicVirtualCollectionInfo () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("collection")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node);
+ assertEquals(1, node.size());
+ }
+
+ @Test
+ public void testGetVirtualCollectionInfoWithAuthentication ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("collection")
+ .request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertNotNull(node);
+ assertTrue(node.isArray());
+ assertEquals(3, node.size());
+ }
+
+ @Test
+ public void testGetVirtualCollectionInfoById () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("collection")
+ .path("GOE-VC").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertNotEquals(0, node.size());
+ assertEquals(node.path("name").asText(), "Goethe Virtual Collection");
+ assertEquals(node.path("description").asText(),
+ "Goethe works from 1810");
+ }
+
+ @Test
+ public void testGetVirtualCollectionInfoByIdUnauthorized ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("collection")
+ .path("WPD15-VC").request().get();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertNotEquals(0, node.size());
+ assertEquals(101, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/2").asText(),
+ "[Cannot found public VirtualCollection with ids: [WPD15-VC]]");
+ }
+
+ @Test
+ public void testGetPublicCorporaInfo () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertTrue(node.isArray());
+ assertEquals(2, node.size());
+ }
+
+ @Test
+ public void testGetCorpusInfoById () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("WPD13").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ // System.out.println(ent);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertTrue(node.isObject());
+ assertEquals(node.path("id").asText(), "WPD13");
+ }
+
+ @Test
+ public void testGetCorpusInfoById2 () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("GOE").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertTrue(node.isObject());
+ assertEquals(node.path("id").asText(), "GOE");
+ }
+
+ @Test
+ public void testGetPublicFoundriesInfo () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("foundry").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertTrue(node.isArray());
+ assertEquals(10, node.size());
+ }
+
+ @Test
+ public void testGetFoundryInfoById () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("foundry")
+ .path("tt").request().get();
+ String ent = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertNotEquals(0, node.size());
+ }
+
+ @Test
+ public void testGetUnexistingCorpusInfo () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("ZUW19").request().get();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertNotEquals(0, node.size());
+ assertEquals(101, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/2").asText(),
+ "[Cannot found public Corpus with ids: [ZUW19]]");
+ }
+
+ // EM: queries for an unauthorized corpus get the same responses /
+ // treatment as
+ // asking for an unexisting corpus info. Does it need a specific
+ // exception instead?
+ @Test
+ public void testGetUnauthorizedCorpusInfo () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("corpus")
+ .path("BRZ10").request().get();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertNotEquals(0, node.size());
+ assertEquals(101, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/2").asText(),
+ "[Cannot found public Corpus with ids: [BRZ10]]");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/SearchControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/SearchControllerTest.java
new file mode 100644
index 0000000..9e932fc
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/SearchControllerTest.java
@@ -0,0 +1,452 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * @author hanl, margaretha
+ * @lastUpdate 18/03/2019
+ */
+public class SearchControllerTest extends SpringJerseyTest {
+
+ @Autowired
+ private KustvaktConfiguration config;
+
+ private JsonNode requestSearchWithFields (String fields)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("fields", fields).queryParam("context", "sentence")
+ .queryParam("count", "13").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ return node;
+ }
+
+ private String createJsonQuery () {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[orth=der]", "poliqarp");
+ s.setCollection("corpusSigle=GOE");
+ s.setQuery("Wasser", "poliqarp");
+ return s.toJSON();
+ }
+
+ @Test
+ public void testApiWelcomeMessage () {
+ Response response = target().path(API_VERSION).path("").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ assertEquals(response.getHeaders().getFirst("X-Index-Revision"),
+ "Wes8Bd4h1OypPqbWF5njeQ==");
+ String message = response.readEntity(String.class);
+ assertEquals(message, config.getApiWelcomeMessage());
+ }
+
+ @Test
+ public void testSearchShowTokens () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .queryParam("show-tokens", true).request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(3, node.at("/matches/0/tokens").size());
+ assertFalse(node.at("/matches/0/snippet").isMissingNode());
+ }
+
+ @Test
+ public void testSearchDisableSnippet () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .queryParam("show-snippet", false)
+ .queryParam("show-tokens", true).request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertTrue(node.at("/matches/0/snippet").isMissingNode());
+ assertEquals(3, node.at("/matches/0/tokens").size());
+ }
+
+ @Test
+ public void testSearchWithField () throws KustvaktException {
+ JsonNode node = requestSearchWithFields("author");
+ assertNotEquals(0, node.at("/matches").size());
+ assertEquals(node.at("/meta/fields").toString(), "[\"author\"]");
+ assertTrue(node.at("/matches/0/tokens").isMissingNode());
+ }
+
+ @Test
+ public void testSearchWithMultipleFields () throws KustvaktException {
+ JsonNode node = requestSearchWithFields("author, title");
+ assertNotEquals(0, node.at("/matches").size());
+ assertEquals(node.at("/meta/fields").toString(),
+ "[\"author\",\"title\"]");
+ }
+
+ @Test
+ public void testSearchQueryPublicCorpora () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .request().accept(MediaType.APPLICATION_JSON).get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:doc");
+ assertEquals(node.at("/collection/key").asText(), "availability");
+ assertEquals(node.at("/collection/value").asText(), "CC-BY.*");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(FREE)");
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ }
+
+ @Test
+ public void testSearchQueryFailure () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der").queryParam("ql", "poliqarp")
+ .queryParam("cq", "corpusSigle=WPD | corpusSigle=GOE")
+ .queryParam("count", "13").request()
+ .accept(MediaType.APPLICATION_JSON).get();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(302, node.at("/errors/0/0").asInt());
+ assertEquals(302, node.at("/errors/1/0").asInt());
+ assertTrue(node.at("/errors/2").isMissingNode());
+ assertFalse(node.at("/collection").isMissingNode());
+ assertEquals(13, node.at("/meta/count").asInt());
+ }
+
+ @Test
+ public void testSearchQueryWithMeta () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=Bachelor]").queryParam("ql", "poliqarp")
+ .queryParam("cutoff", "true").queryParam("count", "5")
+ .queryParam("page", "1").queryParam("context", "40-t,30-t")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertTrue(node.at("/meta/cutOff").asBoolean());
+ assertEquals(5, node.at("/meta/count").asInt());
+ assertEquals(0, node.at("/meta/startIndex").asInt());
+ assertEquals(node.at("/meta/context/left/0").asText(), "token");
+ assertEquals(40, node.at("/meta/context/left/1").asInt());
+ assertEquals(30, node.at("/meta/context/right/1").asInt());
+ assertEquals(-1, node.at("/meta/totalResults").asInt());
+ for (String path : new String[] { "/meta/count", "/meta/startIndex",
+ "/meta/context/left/1", "/meta/context/right/1",
+ "/meta/totalResults", "/meta/itemsPerPage" }) {
+ assertTrue(node.at(path).isNumber(), path + " should be a number");
+ }
+ }
+
+ @Test
+ public void testSearchQueryFreeExtern () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .request().header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node);
+ assertNotEquals(0, node.path("matches").size());
+ assertEquals(node.at("/collection/@type").asText(), "koral:doc");
+ assertEquals(node.at("/collection/key").asText(), "availability");
+ assertEquals(node.at("/collection/value").asText(), "CC-BY.*");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(FREE)");
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ }
+
+ @Test
+ public void testSearchQueryFreeIntern () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .request().header(HttpHeaders.X_FORWARDED_FOR, "172.27.0.32")
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node);
+ assertNotEquals(0, node.path("matches").size());
+ assertEquals(node.at("/collection/@type").asText(), "koral:doc");
+ assertEquals(node.at("/collection/key").asText(), "availability");
+ assertEquals(node.at("/collection/value").asText(), "CC-BY.*");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(FREE)");
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ }
+
+ @Test
+ public void testSearchQueryExternAuthorized () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ // System.out.println(entity);
+ assertNotNull(node);
+ assertNotEquals(0, node.path("matches").size());
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "CC-BY.*");
+ assertEquals(
+ node.at("/collection/operands/1/operands/0/value").asText(),
+ "ACA.*");
+ assertEquals(
+ node.at("/collection/operands/1/operands/1/value").asText(),
+ "QAO-NC");
+ assertEquals(node.at("/collection/operation").asText(), "operation:or");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(PUB)");
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ }
+
+ @Test
+ public void testSearchQueryInternAuthorized () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "172.27.0.32").get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node);
+ assertNotEquals(0, node.path("matches").size());
+ // System.out.println(node);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "CC-BY.*");
+ assertEquals(
+ node.at("/collection/operands/1/operands/0/value").asText(),
+ "ACA.*");
+ assertEquals(
+ node.at("/collection/operands/1/operands/1/operands/0/value")
+ .asText(),
+ "QAO-NC");
+ assertEquals(
+ node.at("/collection/operands/1/operands/1/operands/1/value")
+ .asText(),
+ "QAO.*");
+ assertEquals(node.at("/collection/operation").asText(), "operation:or");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(ALL)");
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ }
+
+ @Test
+ public void testSearchWithCorpusQuery () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "corpusTitle=gingko").request()
+ .accept(MediaType.APPLICATION_JSON).get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(2, node.at("/collection/operands").size());
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "CC-BY.*");
+ assertEquals(node.at("/collection/operands/1/value").asText(),
+ "gingko");
+ assertEquals(node.at("/collection/operands/1/match").asText(),
+ "match:eq");
+ assertTrue(node.at("/collection/operands/1/type").isMissingNode());
+ }
+
+ @Test
+ public void testSearchQueryWithCollectionQueryAuthorizedWithoutIP ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "textClass=politik & corpusSigle=BRZ10")
+ .request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertNotNull(node);
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(FREE)");
+ // EM: double AND operations
+ assertEquals(node.at("/collection/operands/0/key").asText(),
+ "availability");
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "CC-BY.*");
+ assertEquals(node.at("/collection/operands/1/operands/0/key").asText(),
+ "textClass");
+ assertEquals(node.at("/collection/operands/1/operands/1/key").asText(),
+ "corpusSigle");
+ }
+
+ @Test
+ @Disabled
+ public void testSearchQueryAuthorizedWithoutIP () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertNotNull(node);
+ assertNotEquals(0, node.path("matches").size());
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "CC-BY.*");
+ assertEquals(node.at("/collection/operands/1/value").asText(), "ACA.*");
+ assertEquals(node.at("/collection/operation").asText(), "operation:or");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(PUB)");
+ }
+
+ @Test
+ public void testSearchWithInvalidPage () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .queryParam("page", "0").request().get();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(), "page must start from 1");
+ }
+
+ @Test
+ public void testSearchSentenceMeta () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("context", "sentence").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(node.at("/meta/context").asText(), "base/s:s");
+ assertNotEquals("/meta/version", "${project.version}");
+ }
+
+ // EM: The API is disabled
+ @Disabled
+ @Test
+ public void testSearchSimpleCQL () throws KustvaktException {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("(der) or (das)", "CQL");
+ Response response = target().path(API_VERSION).path("search").request()
+ .post(Entity.json(s.toJSON()));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertNotEquals(0, node.path("matches").size());
+ // assertEquals(17027, node.at("/meta/totalResults").asInt());
+ }
+
+ // EM: The API is disabled
+ @Test
+ @Disabled
+ public void testSearchRawQuery () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search").request()
+ .post(Entity.json(createJsonQuery()));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertNotEquals(0, node.path("matches").size());
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(FREE)");
+ }
+
+ // EM: The API is disabled
+ @Test
+ @Disabled
+ public void testSearchPostAll () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "10.27.0.32")
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .post(Entity.json(createJsonQuery()));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertNotEquals(0, node.path("matches").size());
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(ALL)");
+ }
+
+ // EM: The API is disabled
+ @Test
+ @Disabled
+ public void testSearchPostPublic () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("kustvakt",
+ "kustvakt2015"))
+ .post(Entity.json(createJsonQuery()));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertNotEquals(0, node.path("matches").size());
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(PUB)");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/SearchNetworkEndpointTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/SearchNetworkEndpointTest.java
new file mode 100644
index 0000000..ef42760
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/SearchNetworkEndpointTest.java
@@ -0,0 +1,113 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockserver.integration.ClientAndServer.startClientAndServer;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.Header;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.ws.rs.core.Response;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class SearchNetworkEndpointTest extends SpringJerseyTest {
+
+ @Autowired
+ private KustvaktConfiguration config;
+
+ private ClientAndServer mockServer;
+
+ private MockServerClient mockClient;
+
+ private int port = 6081;
+
+ private String searchResult;
+
+ private String endpointURL = "http://localhost:" + port + "/searchEndpoint";
+
+ public SearchNetworkEndpointTest () throws IOException {
+ searchResult = IOUtils.toString(
+ ClassLoader.getSystemResourceAsStream(
+ "network-output/search-result.jsonld"),
+ StandardCharsets.UTF_8);
+ }
+
+ @BeforeEach
+ public void startMockServer () {
+ mockServer = startClientAndServer(port);
+ mockClient = new MockServerClient("localhost", mockServer.getPort());
+ }
+
+ @AfterEach
+ public void stopMockServer () {
+ mockServer.stop();
+ }
+
+ @Test
+ public void testSearchNetwork ()
+ throws IOException, KustvaktException, URISyntaxException {
+ config.setNetworkEndpointURL(endpointURL);
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/searchEndpoint")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody(searchResult).withStatusCode(200));
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("engine", "network").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/matches").size());
+ }
+
+ @Test
+ public void testSearchWithUnknownURL ()
+ throws IOException, KustvaktException {
+ config.setNetworkEndpointURL("http://localhost:1040/search");
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("engine", "network").request().get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.SEARCH_NETWORK_ENDPOINT_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testSearchWithUnknownHost () throws KustvaktException {
+ config.setNetworkEndpointURL("http://search.com");
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("engine", "network").request().get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.SEARCH_NETWORK_ENDPOINT_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/SearchPipeTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/SearchPipeTest.java
new file mode 100644
index 0000000..03a019e
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/SearchPipeTest.java
@@ -0,0 +1,315 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockserver.integration.ClientAndServer.startClientAndServer;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.Header;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.ws.rs.core.Response;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class SearchPipeTest extends SpringJerseyTest {
+
+ private ClientAndServer mockServer;
+
+ private MockServerClient mockClient;
+
+ private int port = 6071;
+
+ private String pipeJson, pipeWithParamJson;
+
+ private String glemmUri = "http://localhost:" + port + "/glemm";
+
+ public SearchPipeTest () throws URISyntaxException, IOException {
+ pipeJson = IOUtils.toString(
+ ClassLoader.getSystemResourceAsStream(
+ "pipe-output/test-pipes.jsonld"),
+ StandardCharsets.UTF_8);
+ pipeWithParamJson = IOUtils.toString(
+ ClassLoader.getSystemResourceAsStream(
+ "pipe-output/with-param.jsonld"),
+ StandardCharsets.UTF_8);
+ }
+
+ @BeforeEach
+ public void startMockServer () {
+ mockServer = startClientAndServer(port);
+ mockClient = new MockServerClient("localhost", mockServer.getPort());
+ }
+
+ @AfterEach
+ public void stopMockServer () {
+ mockServer.stop();
+ }
+
+ @Test
+ public void testMockServer () throws IOException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/test")
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody("{test}").withStatusCode(200));
+ URL url = new URL("http://localhost:" + port + "/test");
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type",
+ "application/json; charset=UTF-8");
+ connection.setRequestProperty("Accept", "application/json");
+ connection.setDoOutput(true);
+ String json = "{\"name\" : \"dory\"}";
+ try (OutputStream os = connection.getOutputStream()) {
+ byte[] input = json.getBytes("utf-8");
+ os.write(input, 0, input.length);
+ }
+ assertEquals(200, connection.getResponseCode());
+ BufferedReader br = new BufferedReader(
+ new InputStreamReader(connection.getInputStream(), "utf-8"));
+ assertEquals(br.readLine(), "{test}");
+ }
+
+ @Test
+ public void testSearchWithPipes ()
+ throws IOException, KustvaktException, URISyntaxException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/glemm")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody(pipeJson).withStatusCode(200));
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", glemmUri).request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/query/wrap/key").size());
+ assertEquals(1, node.at("/collection/rewrites").size());
+ assertEquals(node.at("/collection/rewrites/0/operation").asText(),
+ "operation:insertion");
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(FREE)");
+ node = node.at("/query/wrap/rewrites");
+ assertEquals(2, node.size());
+ assertEquals(node.at("/0/src").asText(), "Glemm");
+ assertEquals(node.at("/0/operation").asText(), "operation:override");
+ assertEquals(node.at("/0/scope").asText(), "key");
+ assertEquals(node.at("/1/src").asText(), "Kustvakt");
+ assertEquals(node.at("/1/operation").asText(), "operation:injection");
+ assertEquals(node.at("/1/scope").asText(), "foundry");
+ }
+
+ @Test
+ public void testSearchWithUrlEncodedPipes ()
+ throws IOException, KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/glemm")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody(pipeJson).withStatusCode(200));
+ glemmUri = URLEncoder.encode(glemmUri, "utf-8");
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", glemmUri).request().get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/query/wrap/key").size());
+ }
+
+ @Test
+ public void testSearchWithMultiplePipes () throws KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/glemm")
+ .withQueryStringParameter("param").withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody(pipeWithParamJson).withStatusCode(200));
+ String glemmUri2 = glemmUri + "?param=blah";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", glemmUri + "," + glemmUri2).request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(3, node.at("/query/wrap/key").size());
+ }
+
+ @Test
+ public void testSearchWithUnknownURL ()
+ throws IOException, KustvaktException {
+ String url = target().getUri().toString() + API_VERSION
+ + "/test/tralala";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", url).request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/3").asText(), "404 Not Found");
+ }
+
+ @Test
+ public void testSearchWithUnknownHost () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", "http://glemm").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/3").asText(), "glemm");
+ }
+
+ @Test
+ public void testSearchUnsupportedMediaType () throws KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/non-json-pipe"))
+ .respond(response().withStatusCode(415));
+ String pipeUri = "http://localhost:" + port + "/non-json-pipe";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", pipeUri).request().get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/3").asText(),
+ "415 Unsupported Media Type");
+ }
+
+ @Test
+ public void testSearchWithMultiplePipeWarnings () throws KustvaktException {
+ String url = target().getUri().toString() + API_VERSION
+ + "/test/tralala";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", url + "," + "http://glemm").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/warnings").size());
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/0/0").asInt());
+ assertEquals(url, node.at("/warnings/0/2").asText());
+ assertEquals(node.at("/warnings/0/3").asText(), "404 Not Found");
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/1/0").asInt());
+ assertEquals(node.at("/warnings/1/2").asText(), "http://glemm");
+ assertEquals(node.at("/warnings/1/3").asText(), "glemm");
+ }
+
+ @Test
+ public void testSearchWithInvalidJsonResponse () throws KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/invalid-response")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response().withBody("{blah:}").withStatusCode(200)
+ .withHeaders(new Header("Content-Type",
+ "application/json; charset=utf-8")));
+ String pipeUri = "http://localhost:" + port + "/invalid-response";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", pipeUri).request().get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.DESERIALIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testSearchWithPlainTextResponse () throws KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/plain-text")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response().withBody("blah").withStatusCode(200));
+ String pipeUri = "http://localhost:" + port + "/plain-text";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", pipeUri).request().get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.DESERIALIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testSearchWithMultipleAndUnknownPipes ()
+ throws KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/glemm")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody(pipeJson).withStatusCode(200));
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", "http://unknown" + "," + glemmUri)
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/query/wrap/key").size());
+ assertTrue(node.at("/warnings").isMissingNode());
+ response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", glemmUri + ",http://unknown").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ entity = response.readEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/0/0").asInt());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/SearchPublicMetadataTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/SearchPublicMetadataTest.java
new file mode 100644
index 0000000..aeec05e
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/SearchPublicMetadataTest.java
@@ -0,0 +1,150 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.query.serialize.MetaQueryBuilder;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class SearchPublicMetadataTest extends SpringJerseyTest {
+
+ @Test
+ public void testSearchPublicMetadata () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Sonne").queryParam("ql", "poliqarp")
+ .queryParam("access-rewrite-disabled", "true").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(ALL)");
+ assertTrue(node.at("/matches/0/snippet").isMissingNode());
+ }
+
+ @Test
+ public void testSearchPublicMetadataExtern () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Sonne").queryParam("ql", "poliqarp")
+ .queryParam("access-rewrite-disabled", "true").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(ALL)");
+ assertTrue(node.at("/matches/0/snippet").isMissingNode());
+ }
+
+ @Test
+ public void testSearchPublicMetadataWithCustomFields ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Sonne").queryParam("ql", "poliqarp")
+ .queryParam("fields", "author,title")
+ .queryParam("access-rewrite-disabled", "true").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(ALL)");
+ assertTrue(node.at("/matches/0/snippet").isMissingNode());
+ assertEquals(node.at("/matches/0/author").asText(),
+ "Goethe, Johann Wolfgang von");
+ assertEquals(node.at("/matches/0/title").asText(),
+ "Italienische Reise");
+ // assertEquals(3, node.at("/matches/0").size());
+ }
+
+ @Test
+ public void testSearchPublicMetadataWithNonPublicField ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Sonne").queryParam("ql", "poliqarp")
+ .queryParam("fields", "author,title,snippet")
+ .queryParam("access-rewrite-disabled", "true").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.NON_PUBLIC_FIELD_IGNORED,
+ node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/1").asText(),
+ "The requested non public fields are ignored");
+ assertEquals(node.at("/warnings/0/2").asText(), "snippet");
+ }
+
+ // EM: The API is disabled
+ @Disabled
+ @Test
+ public void testSearchPostPublicMetadata () throws KustvaktException {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[orth=der]", "poliqarp");
+ s.setCollection("corpusSigle=GOE");
+ s.setQuery("Wasser", "poliqarp");
+ MetaQueryBuilder meta = new MetaQueryBuilder();
+ meta.addEntry("snippets", "true");
+ s.setMeta(meta);
+ Response response = target().path(API_VERSION).path("search").request()
+ .post(Entity.json(s.toJSON()));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/collection/rewrites/0/scope").asText(),
+ "availability(ALL)");
+ assertTrue(node.at("/matches/0/snippet").isMissingNode());
+ }
+
+ @Test
+ public void testSearchPublicMetadataWithSystemVC ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Sonne").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo system-vc")
+ .queryParam("access-rewrite-disabled", "true").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ node = node.at("/collection/operands/1");
+ assertEquals(node.at("/@type").asText(), "koral:doc");
+ assertEquals(node.at("/value").asText(), "GOE");
+ assertEquals(node.at("/match").asText(), "match:eq");
+ assertEquals(node.at("/key").asText(), "corpusSigle");
+ assertEquals(node.at("/rewrites/0/operation").asText(),
+ "operation:deletion");
+ assertEquals(node.at("/rewrites/0/scope").asText(),
+ "@type(koral:docGroupRef)");
+ assertEquals(node.at("/rewrites/1/operation").asText(),
+ "operation:deletion");
+ assertEquals(node.at("/rewrites/1/scope").asText(), "ref(system-vc)");
+ assertEquals(node.at("/rewrites/2/operation").asText(),
+ "operation:insertion");
+ }
+
+ @Test
+ public void testSearchPublicMetadataWithPrivateVC ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Sonne").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"dory/dory-vc\"")
+ .queryParam("access-rewrite-disabled", "true").request().get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/2").asText(), "guest");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/SearchTokenSnippetTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/SearchTokenSnippetTest.java
new file mode 100644
index 0000000..e115f90
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/SearchTokenSnippetTest.java
@@ -0,0 +1,69 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.ws.rs.core.Response;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class SearchTokenSnippetTest extends SpringJerseyTest {
+
+ @Test
+ public void testSearchWithTokens () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("show-tokens", "true")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertTrue(node.at("/matches/0/hasSnippet").asBoolean());
+ assertTrue(node.at("/matches/0/hasTokens").asBoolean());
+ assertTrue(node.at("/matches/0/tokens/left").size() > 0);
+ assertTrue(node.at("/matches/0/tokens/right").size() > 0);
+ assertEquals(1, node.at("/matches/0/tokens/match").size());
+ }
+
+ @Test
+ public void testSearchWithoutTokens () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("show-tokens", "false")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertTrue(node.at("/matches/0/hasSnippet").asBoolean());
+ assertFalse(node.at("/matches/0/hasTokens").asBoolean());
+ assertTrue(node.at("/matches/0/tokens").isMissingNode());
+ }
+
+ @Test
+ public void testSearchPublicMetadataWithTokens () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("access-rewrite-disabled", "true")
+ .queryParam("show-tokens", "true")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertFalse(node.at("/matches/0/hasSnippet").asBoolean());
+ assertFalse(node.at("/matches/0/hasTokens").asBoolean());
+ assertTrue(node.at("/matches/0/snippet").isMissingNode());
+ assertTrue(node.at("/matches/0/tokens").isMissingNode());
+ assertEquals(StatusCodes.NOT_ALLOWED, node.at("/warnings/0/0").asInt());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java
new file mode 100644
index 0000000..ecdad64
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java
@@ -0,0 +1,216 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.ws.rs.core.Response;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author margaretha, diewald
+ */
+public class StatisticsControllerTest extends SpringJerseyTest {
+
+ @Test
+ public void testGetStatisticsNoResource ()
+ throws IOException, KustvaktException {
+ String corpusQuery = "corpusSigle=WPD15";
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("corpusQuery", corpusQuery).request().get();
+ assert Status.OK.getStatusCode() == response.getStatus();
+ assertEquals(response.getHeaders().getFirst("X-Index-Revision"),
+ "Wes8Bd4h1OypPqbWF5njeQ==");
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.get("documents").asInt(), 0);
+ assertEquals(node.get("tokens").asInt(), 0);
+ }
+
+ @Test
+ public void testStatisticsWithCq () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("cq", "textType=Abhandlung & corpusSigle=GOE")
+ .request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertEquals(2, node.at("/documents").asInt());
+ assertEquals(138180, node.at("/tokens").asInt());
+ assertEquals(5687, node.at("/sentences").asInt());
+ assertEquals(258, node.at("/paragraphs").asInt());
+ assertTrue(node.at("/warnings").isMissingNode());
+ }
+
+ @Test
+ public void testStatisticsWithCqAndCorpusQuery () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("cq", "textType=Abhandlung & corpusSigle=GOE")
+ .queryParam("corpusQuery",
+ "textType=Autobiographie & corpusSigle=GOE")
+ .request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertEquals(2, node.at("/documents").asInt());
+ assertEquals(138180, node.at("/tokens").asInt());
+ assertEquals(5687, node.at("/sentences").asInt());
+ assertEquals(258, node.at("/paragraphs").asInt());
+ assertTrue(node.at("/warnings").isMissingNode());
+ }
+
+ @Test
+ public void testGetStatisticsWithcorpusQuery1 ()
+ throws IOException, KustvaktException {
+ String corpusQuery = "corpusSigle=GOE";
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("corpusQuery", corpusQuery).request().get();
+ assert Status.OK.getStatusCode() == response.getStatus();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.get("documents").asInt(), 11);
+ assertEquals(node.get("tokens").asInt(), 665842);
+ assertEquals(StatusCodes.DEPRECATED, node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/1").asText(),
+ "Parameter corpusQuery is deprecated in favor of cq.");
+ }
+
+ @Test
+ public void testGetStatisticsWithcorpusQuery2 ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("corpusQuery", "creationDate since 1810").request()
+ .get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assert Status.OK.getStatusCode() == response.getStatus();
+ assertEquals(node.get("documents").asInt(), 7);
+ assertEquals(node.get("tokens").asInt(), 279402);
+ assertEquals(node.get("sentences").asInt(), 11047);
+ assertEquals(node.get("paragraphs").asInt(), 489);
+ }
+
+ @Test
+ public void testGetStatisticsWithWrongcorpusQuery ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("corpusQuery", "creationDate geq 1810").request()
+ .get();
+ assert Status.BAD_REQUEST.getStatusCode() == response.getStatus();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/errors/0/0").asInt(), 302);
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Could not parse query >>> (creationDate geq 1810) <<<.");
+ assertEquals(node.at("/errors/0/2").asText(),
+ "(creationDate geq 1810)");
+ }
+
+ @Test
+ public void testGetStatisticsWithWrongcorpusQuery2 ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("corpusQuery", "creationDate >= 1810").request()
+ .get();
+ String ent = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/errors/0/0").asInt(), 305);
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Operator >= is not acceptable.");
+ assertEquals(node.at("/errors/0/2").asText(), ">=");
+ }
+
+ @Test
+ public void testGetStatisticsWithoutcorpusQuery ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(11, node.at("/documents").asInt());
+ assertEquals(665842, node.at("/tokens").asInt());
+ assertEquals(25074, node.at("/sentences").asInt());
+ assertEquals(772, node.at("/paragraphs").asInt());
+ }
+
+ @Test
+ public void testGetStatisticsWithKoralQuery ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .request()
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .post(Entity.json("{ \"collection\" : {\"@type\": "
+ + "\"koral:doc\", \"key\": \"availability\", \"match\": "
+ + "\"match:eq\", \"type\": \"type:regex\", \"value\": "
+ + "\"CC-BY.*\"} }"));
+ assertEquals(response.getHeaders().getFirst("X-Index-Revision"),
+ "Wes8Bd4h1OypPqbWF5njeQ==");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(2, node.at("/documents").asInt());
+ assertEquals(72770, node.at("/tokens").asInt());
+ assertEquals(2985, node.at("/sentences").asInt());
+ assertEquals(128, node.at("/paragraphs").asInt());
+ }
+
+ @Test
+ public void testGetStatisticsWithEmptyCollection ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .request()
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .post(Entity.json("{}"));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/errors/0/0").asInt(),
+ de.ids_mannheim.korap.util.StatusCodes.MISSING_COLLECTION);
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Collection is not found");
+ }
+
+ @Test
+ public void testGetStatisticsWithIncorrectJson ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .request()
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .post(Entity.json("{ \"collection\" : }"));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(StatusCodes.DESERIALIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Failed deserializing json object: { \"collection\" : }");
+ }
+
+ @Test
+ public void testGetStatisticsWithoutKoralQuery ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .request().post(Entity.json(""));
+ String ent = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(11, node.at("/documents").asInt());
+ assertEquals(665842, node.at("/tokens").asInt());
+ assertEquals(25074, node.at("/sentences").asInt());
+ assertEquals(772, node.at("/paragraphs").asInt());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/TokenExpiryTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/TokenExpiryTest.java
new file mode 100644
index 0000000..1d130b6
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/TokenExpiryTest.java
@@ -0,0 +1,104 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.entity.ContentType;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * Before running this test:
+ * set oauth2.access.token.expiry = 2S
+ * oauth2.authorization.code.expiry = 1S
+ *
+ * @author margaretha
+ */
+public class TokenExpiryTest extends SpringJerseyTest {
+
+ @Disabled
+ @Test
+ public void requestToken ()
+ throws KustvaktException, InterruptedException, IOException {
+ Form form = new Form();
+ form.param("grant_type", "password");
+ form.param("client_id", "fCBbQkAyYzI4NzUxMg");
+ form.param("client_secret", "secret");
+ form.param("username", "dory");
+ form.param("password", "password");
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("token").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ String token = node.at("/access_token").asText();
+ Thread.sleep(1000);
+ testRequestAuthorizationCodeAuthenticationTooOld(token);
+ Thread.sleep(1500);
+ testSearchWithExpiredToken(token);
+ }
+
+ // not possible to store expired token in the test database,
+ // because sqlite needs a trigger after INSERT to
+ // oauth_access_token to store created_date. Before INSERT trigger
+ // does not work.
+ private void testSearchWithExpiredToken (String token)
+ throws KustvaktException, IOException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Wasser").queryParam("ql", "poliqarp")
+ .request().header(Attributes.AUTHORIZATION, "Bearer " + token)
+ .get();
+ String ent = response.readEntity(String.class);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(StatusCodes.EXPIRED, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Access token is expired");
+ }
+
+ // cannot be tested dynamically
+ private void testRequestAuthorizationCodeAuthenticationTooOld (String token)
+ throws KustvaktException {
+ Form form = new Form();
+ form.param("response_type", "code");
+ form.param("client_id", "fCBbQkAyYzI4NzUxMg");
+ form.param("redirect_uri",
+ "https://korap.ids-mannheim.de/confidential/redirect");
+ form.param("scope", "search");
+ form.param("max_age", "1");
+
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("authorize").request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + token)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+
+ assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.USER_REAUTHENTICATION_REQUIRED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(
+ "User reauthentication is required because the authentication "
+ + "time is too old according to max_age",
+ node.at("/errors/0/1").asText());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java
new file mode 100644
index 0000000..27c8512
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java
@@ -0,0 +1,79 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.net.URI;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
+
+public class UserControllerTest extends OAuth2TestBase {
+
+ private String username = "User\"ControllerTest";
+
+ private String userAuthHeader;
+
+ public UserControllerTest () throws KustvaktException {
+ userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "password");
+ }
+
+ private OAuth2ClientJson createOAuth2Client () {
+ OAuth2ClientJson client = new OAuth2ClientJson();
+ client.setName("OWID client");
+ client.setType(OAuth2ClientType.PUBLIC);
+ client.setDescription("OWID web-based client");
+ client.setRedirectURI("https://www.owid.de");
+ return client;
+ }
+
+ private String registerClient ()
+ throws ProcessingException, KustvaktException {
+ OAuth2ClientJson clientJson = createOAuth2Client();
+ Response response = registerClient(username, clientJson);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String clientId = node.at("/client_id").asText();
+ return clientId;
+ }
+
+ private String requestOAuth2AccessToken (String clientId)
+ throws KustvaktException {
+ Response response = requestAuthorizationCode("code", clientId, "",
+ "user_info", "", userAuthHeader);
+ String code = parseAuthorizationCode(response);
+ response = requestTokenWithAuthorizationCodeAndForm(clientId, null,
+ code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ String accessToken = node.at("/access_token").asText();
+ return accessToken;
+ }
+
+ @Test
+ public void getUsername () throws ProcessingException, KustvaktException {
+ String clientId = registerClient();
+ String accessToken = requestOAuth2AccessToken(clientId);
+ Response response = target().path(API_VERSION).path("user").path("info")
+ .request()
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(username, node.at("/username").asText());
+ deregisterClient(username, clientId);
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java
new file mode 100644
index 0000000..aa6a4c6
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java
@@ -0,0 +1,356 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MediaType;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+import jakarta.ws.rs.client.Entity;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.constant.GroupMemberStatus;
+import de.ids_mannheim.korap.constant.PredefinedRole;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.service.UserGroupService;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author margaretha
+ */
+public class UserGroupControllerAdminTest extends SpringJerseyTest {
+
+ private String sysAdminUser = "admin";
+
+ private String testUser = "group-admin";
+
+ private JsonNode listGroup (String username)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ return node;
+ }
+
+ @Test
+ public void testListUserGroupsUsingAdminToken () throws KustvaktException {
+ Form f = new Form();
+ f.param("username", "dory");
+ f.param("token", "secret");
+ Response response = target().path(API_VERSION).path("admin")
+ .path("group").path("list").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(f));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(3, node.size());
+ }
+
+ /**
+ * Cannot use admin token
+ * see
+ * {@link UserGroupService#retrieveUserGroupByStatus(String,
+ * String, de.ids_mannheim.korap.constant.UserGroupStatus)}
+ *
+ * @throws KustvaktException
+ */
+ // @Test
+ // public void testListUserGroupsWithAdminToken () throws KustvaktException {
+ // Response response = target().path(API_VERSION).path("group")
+ // .path("list").path("system-admin")
+ // .queryParam("username", "dory")
+ // .queryParam("token", "secret")
+ // .request()
+ // .get();
+ //
+ // assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // String entity = response.readEntity(String.class);
+ // JsonNode node = JsonUtils.readTree(entity);
+ // assertEquals(3, node.size());
+ // }
+ @Test
+ public void testListUserGroupsUnauthorized () throws KustvaktException {
+ Form f = new Form();
+ f.param("username", "dory");
+ Response response = target().path(API_VERSION).path("admin")
+ .path("group").path("list").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(f));
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testListUserGroupsWithStatus () throws KustvaktException {
+ Form f = new Form();
+ f.param("username", "dory");
+ f.param("status", "ACTIVE");
+ Response response = target().path(API_VERSION).path("admin")
+ .path("group").path("list").queryParam("username", "dory")
+ .queryParam("status", "ACTIVE").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ sysAdminUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(f));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.size());
+ }
+
+ // same as list user-groups of the admin
+ @Test
+ public void testListWithoutUsername ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("group").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ sysAdminUser, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ assertEquals(entity, "[]");
+ }
+
+ @Test
+ public void testListByStatusAll ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("admin")
+ .path("group").path("list").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ sysAdminUser, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(null);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ boolean containsHiddenStatus = false;
+ for (int i = 0; i < node.size(); i++) {
+ if (node.get(i).at("/status").asText().equals("HIDDEN")) {
+ containsHiddenStatus = true;
+ }
+ }
+ assertEquals(true, containsHiddenStatus);
+ }
+
+ @Test
+ public void testListByStatusHidden ()
+ throws ProcessingException, KustvaktException {
+ Form f = new Form();
+ f.param("status", "HIDDEN");
+ Response response = target().path(API_VERSION).path("admin")
+ .path("group").path("list").queryParam("status", "HIDDEN")
+ .request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ sysAdminUser, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(f));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(1, node.size());
+ assertEquals(3, node.at("/0/id").asInt());
+ }
+
+ @Test
+ public void testUserGroupAdmin ()
+ throws ProcessingException, KustvaktException {
+ String groupName = "admin-test-group";
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser,
+ "password"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .put(Entity.form(new Form()));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ // list user group
+ JsonNode node = listGroup(testUser);
+ assertEquals(1, node.size());
+ node = node.get(0);
+ assertEquals(groupName, node.get("name").asText());
+ testInviteMember(groupName);
+ testMemberRole("marlin", groupName);
+ testDeleteMember(groupName);
+ testDeleteGroup(groupName);
+ }
+
+ private void testMemberRole (String memberUsername, String groupName)
+ throws ProcessingException, KustvaktException {
+ // accept invitation
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("subscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ memberUsername, "pass"))
+ .post(Entity.form(new Form()));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ testAddMemberRoles(groupName, memberUsername);
+ testDeleteMemberRoles(groupName, memberUsername);
+ }
+
+ private void testAddMemberRoles (String groupName, String memberUsername)
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("memberUsername", memberUsername);
+ // USER_GROUP_ADMIN
+ form.param("roleId", "1");
+ // USER_GROUP_MEMBER
+ form.param("roleId", "2");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("role").path("add").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ sysAdminUser, "password"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = retrieveGroup(groupName).at("/members");
+ JsonNode member;
+ for (int i = 0; i < node.size(); i++) {
+ member = node.get(i);
+ if (member.at("/userId").asText().equals(memberUsername)) {
+ assertEquals(3, member.at("/roles").size());
+ assertEquals(PredefinedRole.USER_GROUP_ADMIN.name(),
+ member.at("/roles/0").asText());
+ break;
+ }
+ }
+ }
+
+ private void testDeleteMemberRoles (String groupName, String memberUsername)
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("memberUsername", memberUsername);
+ // USER_GROUP_ADMIN
+ form.param("roleId", "1");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("role").path("delete").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ sysAdminUser, "password"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = retrieveGroup(groupName).at("/members");
+ JsonNode member;
+ for (int i = 0; i < node.size(); i++) {
+ member = node.get(i);
+ if (member.at("/userId").asText().equals(memberUsername)) {
+ assertEquals(2, member.at("/roles").size());
+ break;
+ }
+ }
+ }
+
+ private JsonNode retrieveGroup (String groupName)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("admin")
+ .path("group").path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ sysAdminUser, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").post(null);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ return node;
+ }
+
+ private void testDeleteGroup (String groupName)
+ throws ProcessingException, KustvaktException {
+ // delete group
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ sysAdminUser, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // check group
+ JsonNode node = listGroup(testUser);
+ assertEquals(0, node.size());
+ }
+
+ private void testDeleteMember (String groupName)
+ throws ProcessingException, KustvaktException {
+ // delete marlin from group
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("~marlin").request()
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ sysAdminUser, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // check group member
+ JsonNode node = listGroup(testUser);
+ node = node.get(0);
+ assertEquals(3, node.get("members").size());
+ assertEquals(node.at("/members/1/userId").asText(), "nemo");
+ assertEquals(GroupMemberStatus.PENDING.name(),
+ node.at("/members/1/status").asText());
+ }
+
+ private void testInviteMember (String groupName)
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("members", "marlin,nemo,darla");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("invite").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(
+ sysAdminUser, "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // list group
+ JsonNode node = listGroup(testUser);
+ node = node.get(0);
+ assertEquals(4, node.get("members").size());
+ assertEquals(node.at("/members/3/userId").asText(), "darla");
+ assertEquals(GroupMemberStatus.PENDING.name(),
+ node.at("/members/1/status").asText());
+ assertEquals(0, node.at("/members/1/roles").size());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
new file mode 100644
index 0000000..5495c85
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
@@ -0,0 +1,853 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Set;
+
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MediaType;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+import jakarta.ws.rs.client.Entity;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.constant.GroupMemberStatus;
+import de.ids_mannheim.korap.constant.PredefinedRole;
+import de.ids_mannheim.korap.dao.UserGroupMemberDao;
+import de.ids_mannheim.korap.entity.Role;
+import de.ids_mannheim.korap.entity.UserGroupMember;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author margaretha
+ */
+public class UserGroupControllerTest extends SpringJerseyTest {
+
+ @Autowired
+ private UserGroupMemberDao memberDao;
+
+ private String username = "UserGroupControllerTest";
+
+ private String admin = "admin";
+
+ private JsonNode retrieveUserGroups (String username)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ return JsonUtils.readTree(entity);
+ }
+
+ private void deleteGroupByName (String groupName) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ // dory is a group admin in dory-group
+ @Test
+ public void testListDoryGroups () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ JsonNode group = node.get(1);
+ assertEquals(2, group.at("/id").asInt());
+ assertEquals(group.at("/name").asText(), "dory-group");
+ assertEquals(group.at("/owner").asText(), "dory");
+ assertEquals(3, group.at("/members").size());
+ }
+
+ // nemo is a group member in dory-group
+ @Test
+ public void testListNemoGroups () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("nemo", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/0/id").asInt());
+ assertEquals(node.at("/0/name").asText(), "dory-group");
+ assertEquals(node.at("/0/owner").asText(), "dory");
+ // group members are not allowed to see other members
+ assertEquals(0, node.at("/0/members").size());
+ }
+
+ // marlin has 2 groups
+ @Test
+ public void testListMarlinGroups () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("marlin", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.size());
+ }
+
+ @Test
+ public void testListGroupGuest () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Unauthorized operation for user: guest");
+ }
+
+ @Test
+ public void testCreateGroupEmptyDescription ()
+ throws ProcessingException, KustvaktException {
+ String groupName = "empty_group";
+ Response response = testCreateUserGroup(groupName, "");
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ deleteGroupByName(groupName);
+ }
+
+ @Test
+ public void testCreateGroupMissingDescription ()
+ throws ProcessingException, KustvaktException {
+ String groupName = "missing-desc-group";
+ Response response = testCreateGroupWithoutDescription(groupName);
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ deleteGroupByName(groupName);
+ }
+
+ private Response testCreateUserGroup (String groupName, String description)
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("description", description);
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .put(Entity.form(form));
+ return response;
+ }
+
+ private Response testCreateGroupWithoutDescription (String groupName)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .put(Entity.form(new Form()));
+ return response;
+ }
+
+ @Test
+ public void testCreateGroupInvalidName ()
+ throws ProcessingException, KustvaktException {
+ String groupName = "invalid-group-name$";
+ Response response = testCreateGroupWithoutDescription(groupName);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ // assertEquals("User-group name must only contains letters, numbers, "
+ // + "underscores, hypens and spaces", node.at("/errors/0/1").asText());
+ assertEquals(node.at("/errors/0/2").asText(), "invalid-group-name$");
+ }
+
+ @Test
+ public void testCreateGroupNameTooShort ()
+ throws ProcessingException, KustvaktException {
+ String groupName = "a";
+ Response response = testCreateGroupWithoutDescription(groupName);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "groupName must contain at least 3 characters");
+ assertEquals(node.at("/errors/0/2").asText(), "groupName");
+ }
+
+ @Test
+ public void testUserGroup () throws ProcessingException, KustvaktException {
+ String groupName = "new-user-group";
+ String description = "This is new-user-group.";
+ Response response = testCreateUserGroup(groupName, description);
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ // same name
+ response = testCreateGroupWithoutDescription(groupName);
+ assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
+ // list user group
+ JsonNode node = retrieveUserGroups(username);
+ assertEquals(1, node.size());
+ node = node.get(0);
+ assertEquals(node.get("name").asText(), "new-user-group");
+ assertEquals(description, node.get("description").asText());
+ assertEquals(username, node.get("owner").asText());
+ assertEquals(1, node.get("members").size());
+ assertEquals(username, node.at("/members/0/userId").asText());
+ assertEquals(GroupMemberStatus.ACTIVE.name(),
+ node.at("/members/0/status").asText());
+ assertEquals(PredefinedRole.VC_ACCESS_ADMIN.name(),
+ node.at("/members/0/roles/1").asText());
+ assertEquals(PredefinedRole.USER_GROUP_ADMIN.name(),
+ node.at("/members/0/roles/0").asText());
+ testUpdateUserGroup(groupName);
+ testInviteMember(groupName);
+ testDeleteMemberUnauthorized(groupName);
+ testDeleteMember(groupName);
+ testDeleteGroup(groupName);
+ testSubscribeToDeletedGroup(groupName);
+ testUnsubscribeToDeletedGroup(groupName);
+ }
+
+ private void testUpdateUserGroup (String groupName)
+ throws ProcessingException, KustvaktException {
+ String description = "Description is updated.";
+ Response response = testCreateUserGroup(groupName, description);
+ assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
+ JsonNode node = retrieveUserGroups(username);
+ assertEquals(1, node.size());
+ assertEquals(description, node.get(0).get("description").asText());
+ }
+
+ private void testDeleteMember (String groupName)
+ throws ProcessingException, KustvaktException {
+ // delete darla from group
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("~darla").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ // check group member
+ response = target().path(API_VERSION).path("group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ node = node.get(0);
+ assertEquals(1, node.get("members").size());
+ }
+
+ private void testDeleteMemberUnauthorized (String groupName)
+ throws ProcessingException, KustvaktException {
+ // nemo is a group member
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("~darla").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("nemo", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Unauthorized operation for user: nemo");
+ }
+
+ // EM: same as cancel invitation
+ private void testDeletePendingMember ()
+ throws ProcessingException, KustvaktException {
+ // dory delete pearl
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("~pearl").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // check member
+ JsonNode node = retrieveUserGroups("pearl");
+ assertEquals(0, node.size());
+ }
+
+ @Test
+ public void testDeleteDeletedMember ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("~pearl").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.GROUP_MEMBER_DELETED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "pearl has already been deleted from the group dory-group");
+ assertEquals(node.at("/errors/0/2").asText(), "[pearl, dory-group]");
+ }
+
+ private void testDeleteGroup (String groupName)
+ throws ProcessingException, KustvaktException {
+ // delete group
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ Form f = new Form();
+ f.param("username", username);
+ f.param("status", "DELETED");
+ // EM: this is so complicated because the group retrieval are not allowed
+ // for delete groups
+ // check group
+ response = target().path(API_VERSION).path("admin").path("group")
+ .path("list").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(f));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ for (int j = 0; j < node.size(); j++) {
+ JsonNode group = node.get(j);
+ // check group members
+ for (int i = 0; i < group.at("/0/members").size(); i++) {
+ assertEquals(GroupMemberStatus.DELETED.name(),
+ group.at("/0/members/" + i + "/status").asText());
+ }
+ }
+ }
+
+ @Test
+ public void testDeleteGroupUnauthorized ()
+ throws ProcessingException, KustvaktException {
+ // dory is a group admin in marlin-group
+ Response response = target().path(API_VERSION).path("group")
+ .path("@marlin-group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Unauthorized operation for user: dory");
+ }
+
+ @Test
+ public void testDeleteDeletedGroup ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@deleted-group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.GROUP_DELETED, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Group deleted-group has been deleted.");
+ assertEquals(node.at("/errors/0/2").asText(), "deleted-group");
+ }
+
+ @Test
+ public void testDeleteGroupOwner ()
+ throws ProcessingException, KustvaktException {
+ // delete marlin from marlin-group
+ // dory is a group admin in marlin-group
+ Response response = target().path(API_VERSION).path("group")
+ .path("@marlin-group").path("~marlin").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.NOT_ALLOWED, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Operation 'delete group owner'is not allowed.");
+ }
+
+ private void testInviteMember (String groupName)
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("members", "darla");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("invite").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // list group
+ response = target().path(API_VERSION).path("group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ node = node.get(0);
+ assertEquals(2, node.get("members").size());
+ assertEquals(node.at("/members/1/userId").asText(), "darla");
+ assertEquals(GroupMemberStatus.PENDING.name(),
+ node.at("/members/1/status").asText());
+ assertEquals(0, node.at("/members/1/roles").size());
+ }
+
+ private void testInviteDeletedMember ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("members", "marlin");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("invite").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // check member
+ JsonNode node = retrieveUserGroups("marlin");
+ assertEquals(2, node.size());
+ JsonNode group = node.get(1);
+ assertEquals(GroupMemberStatus.PENDING.name(),
+ group.at("/userMemberStatus").asText());
+ }
+
+ @Test
+ public void testInviteDeletedMember2 ()
+ throws ProcessingException, KustvaktException {
+ // pearl has status deleted in dory-group
+ Form form = new Form();
+ form.param("members", "pearl");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("invite").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // check member
+ JsonNode node = retrieveUserGroups("pearl");
+ assertEquals(1, node.size());
+ JsonNode group = node.get(0);
+ assertEquals(GroupMemberStatus.PENDING.name(),
+ group.at("/userMemberStatus").asText());
+ testDeletePendingMember();
+ }
+
+ @Test
+ public void testInvitePendingMember ()
+ throws ProcessingException, KustvaktException {
+ // marlin has status PENDING in dory-group
+ Form form = new Form();
+ form.param("members", "marlin");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("invite").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .post(Entity.form(form));
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.GROUP_MEMBER_EXISTS,
+ node.at("/errors/0/0").asInt());
+ assertEquals(
+ "Username marlin with status PENDING exists in the user-group "
+ + "dory-group",
+ node.at("/errors/0/1").asText());
+ assertEquals(node.at("/errors/0/2").asText(),
+ "[marlin, PENDING, dory-group]");
+ }
+
+ @Test
+ public void testInviteActiveMember ()
+ throws ProcessingException, KustvaktException {
+ // nemo has status active in dory-group
+ Form form = new Form();
+ form.param("members", "nemo");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("invite").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.GROUP_MEMBER_EXISTS,
+ node.at("/errors/0/0").asInt());
+ assertEquals(
+ "Username nemo with status ACTIVE exists in the user-group "
+ + "dory-group",
+ node.at("/errors/0/1").asText());
+ assertEquals(node.at("/errors/0/2").asText(),
+ "[nemo, ACTIVE, dory-group]");
+ }
+
+ @Test
+ public void testInviteMemberToDeletedGroup ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("members", "nemo");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@deleted-group").path("invite").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.GROUP_DELETED, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Group deleted-group has been deleted.");
+ assertEquals(node.at("/errors/0/2").asText(), "deleted-group");
+ }
+
+ // marlin has GroupMemberStatus.PENDING in dory-group
+ @Test
+ public void testSubscribePendingMember () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("subscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("marlin", "pass"))
+ .post(Entity.form(new Form()));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // retrieve marlin group
+ JsonNode node = retrieveUserGroups("marlin");
+ // System.out.println(node);
+ assertEquals(2, node.size());
+ JsonNode group = node.get(1);
+ assertEquals(2, group.at("/id").asInt());
+ assertEquals(group.at("/name").asText(), "dory-group");
+ assertEquals(group.at("/owner").asText(), "dory");
+ // group members are not allowed to see other members
+ assertEquals(0, group.at("/members").size());
+ assertEquals(GroupMemberStatus.ACTIVE.name(),
+ group.at("/userMemberStatus").asText());
+ assertEquals(PredefinedRole.VC_ACCESS_MEMBER.name(),
+ group.at("/userRoles/1").asText());
+ assertEquals(PredefinedRole.USER_GROUP_MEMBER.name(),
+ group.at("/userRoles/0").asText());
+ // unsubscribe marlin from dory-group
+ testUnsubscribeActiveMember("dory-group");
+ checkGroupMemberRole("dory-group", "marlin");
+ // invite marlin to dory-group to set back the
+ // GroupMemberStatus.PENDING
+ testInviteDeletedMember();
+ }
+
+ // pearl has GroupMemberStatus.DELETED in dory-group
+ @Test
+ public void testSubscribeDeletedMember () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("subscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("pearl", "pass"))
+ .post(Entity.form(new Form()));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.GROUP_MEMBER_DELETED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "pearl has already been deleted from the group dory-group");
+ }
+
+ @Test
+ public void testSubscribeMissingGroupName () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("subscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("bruce", "pass"))
+ .post(Entity.form(new Form()));
+ assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testSubscribeNonExistentMember () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("subscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("bruce", "pass"))
+ .post(Entity.form(new Form()));
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.GROUP_MEMBER_NOT_FOUND,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "bruce is not found in the group");
+ }
+
+ @Test
+ public void testSubscribeToNonExistentGroup () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@non-existent").path("subscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("pearl", "pass"))
+ .post(Entity.form(new Form()));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Group non-existent is not found");
+ }
+
+ private void testSubscribeToDeletedGroup (String groupName)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("subscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("nemo", "pass"))
+ .post(Entity.form(new Form()));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.GROUP_DELETED, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Group new-user-group has been deleted.");
+ }
+
+ private void testUnsubscribeActiveMember (String groupName)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("unsubscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("marlin", "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = retrieveUserGroups("marlin");
+ assertEquals(1, node.size());
+ }
+
+ private void checkGroupMemberRole (String groupName,
+ String deletedMemberName) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("admin")
+ .path("group").path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .post(null);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity).at("/members");
+ JsonNode member;
+ for (int i = 0; i < node.size(); i++) {
+ member = node.get(i);
+ if (deletedMemberName.equals(member.at("/userId").asText())) {
+ assertEquals(0, node.at("/roles").size());
+ break;
+ }
+ }
+ }
+
+ @Test
+ public void testUnsubscribeDeletedMember ()
+ throws ProcessingException, KustvaktException {
+ // pearl unsubscribes from dory-group
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("unsubscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("pearl", "pass"))
+ .delete();
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.GROUP_MEMBER_DELETED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "pearl has already been deleted from the group dory-group");
+ assertEquals(node.at("/errors/0/2").asText(), "[pearl, dory-group]");
+ }
+
+ @Test
+ public void testUnsubscribePendingMember ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = retrieveUserGroups("marlin");
+ assertEquals(2, node.size());
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("unsubscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("marlin", "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = retrieveUserGroups("marlin");
+ assertEquals(1, node.size());
+ // invite marlin to dory-group to set back the
+ // GroupMemberStatus.PENDING
+ testInviteDeletedMember();
+ }
+
+ @Test
+ public void testUnsubscribeMissingGroupName () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("unsubscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("marlin", "pass"))
+ .delete();
+ assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testUnsubscribeNonExistentMember () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@dory-group").path("unsubscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("bruce", "pass"))
+ .delete();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.GROUP_MEMBER_NOT_FOUND,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "bruce is not found in the group");
+ }
+
+ @Test
+ public void testUnsubscribeToNonExistentGroup () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@tralala-group").path("unsubscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("pearl", "pass"))
+ .delete();
+ assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Group tralala-group is not found");
+ }
+
+ private void testUnsubscribeToDeletedGroup (String groupName)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("unsubscribe").request()
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("nemo", "pass"))
+ .delete();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.GROUP_DELETED, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Group new-user-group has been deleted.");
+ }
+
+ @Test
+ public void testAddSameMemberRole ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("memberUsername", "dory");
+ form.param("roleId", "1");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@marlin-group").path("role").path("add").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("marlin", "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ UserGroupMember member = memberDao.retrieveMemberById("dory", 1);
+ Set<Role> roles = member.getRoles();
+ assertEquals(2, roles.size());
+ }
+
+ @Test
+ public void testDeleteAddMemberRole ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("memberUsername", "dory");
+ form.param("roleId", "1");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@marlin-group").path("role").path("delete").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("marlin", "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ UserGroupMember member = memberDao.retrieveMemberById("dory", 1);
+ Set<Role> roles = member.getRoles();
+ assertEquals(1, roles.size());
+ testAddSameMemberRole();
+ }
+
+ @Test
+ public void testEditMemberRoleEmpty ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("memberUsername", "dory");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@marlin-group").path("role").path("edit").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("marlin", "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ UserGroupMember member = memberDao.retrieveMemberById("dory", 1);
+ Set<Role> roles = member.getRoles();
+ assertEquals(0, roles.size());
+ testEditMemberRole();
+ }
+
+ private void testEditMemberRole ()
+ throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("memberUsername", "dory");
+ form.param("roleId", "1");
+ form.param("roleId", "3");
+ Response response = target().path(API_VERSION).path("group")
+ .path("@marlin-group").path("role").path("edit").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("marlin", "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ UserGroupMember member = memberDao.retrieveMemberById("dory", 1);
+ Set<Role> roles = member.getRoles();
+ assertEquals(2, roles.size());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/UserSettingControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/UserSettingControllerTest.java
new file mode 100644
index 0000000..176e31b
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/UserSettingControllerTest.java
@@ -0,0 +1,241 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import jakarta.ws.rs.client.Entity;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author margaretha
+ */
+public class UserSettingControllerTest extends SpringJerseyTest {
+
+ private String username = "UserSetting_Test";
+
+ private String username2 = "UserSetting.Test2";
+
+ public Response sendPutRequest (String username, Map<String, Object> map)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .put(Entity.json(map));
+ return response;
+ }
+
+ @Test
+ public void testCreateSettingWithJson () throws KustvaktException {
+ String json = "{\"pos-foundry\":\"opennlp\",\"metadata\":[\"author\", \"title\","
+ + "\"textSigle\", \"availability\"],\"resultPerPage\":25}";
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .put(Entity.json(json));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ int numOfResult = 25;
+ String metadata = "[\"author\",\"title\",\"textSigle\","
+ + "\"availability\"]";
+ testRetrieveSettings(username, "opennlp", numOfResult, metadata, true);
+ testDeleteKeyNotExist(username);
+ testDeleteKey(username, numOfResult, metadata, true);
+ testDeleteSetting(username);
+ }
+
+ @Test
+ public void testCreateSettingWithMap () throws KustvaktException {
+ Map<String, Object> map = new HashMap<>();
+ map.put("pos-foundry", "opennlp");
+ map.put("resultPerPage", 25);
+ map.put("metadata", "author title textSigle availability");
+ Response response = sendPutRequest(username2, map);
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ testRetrieveSettings(username2, "opennlp", 25,
+ "author title textSigle availability", false);
+ testUpdateSetting(username2);
+ testputRequestInvalidKey();
+ }
+
+ @Test
+ public void testputRequestInvalidKey () throws KustvaktException {
+ Map<String, Object> map = new HashMap<>();
+ map.put("key/", "invalidKey");
+ Response response = sendPutRequest(username2, map);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/2").asText(), "key/");
+ }
+
+ @Test
+ public void testPutDifferentUsername () throws KustvaktException {
+ String json = "{\"pos-foundry\":\"opennlp\",\"metadata\":\"author title "
+ + "textSigle availability\",\"resultPerPage\":25}";
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username2, "pass"))
+ .put(Entity.json(json));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testGetDifferentUsername () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username2, "pass"))
+ .get();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testGetSettingNotExist () throws KustvaktException {
+ String username = "tralala";
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .get();
+ assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ assertEquals(
+ "No default setting for username: " + username + " is found",
+ node.at("/errors/0/1").asText());
+ assertEquals(username, node.at("/errors/0/2").asText());
+ }
+
+ @Test
+ public void testDeleteSettingNotExist () throws KustvaktException {
+ String username = "tralala";
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testDeleteKeyDifferentUsername () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").path("pos-foundry").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username2, "pass"))
+ .delete();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ }
+
+ private void testDeleteSetting (String username) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ response = target().path(API_VERSION).path("~" + username)
+ .path("setting").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ assertEquals(username, node.at("/errors/0/2").asText());
+ }
+
+ // EM: deleting a non-existing key does not throw an error,
+ // because
+ // the purpose of the request has been achieved.
+ private void testDeleteKeyNotExist (String username)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").path("lemma-foundry").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ private void testDeleteKey (String username, int numOfResult,
+ String metadata, boolean isMetadataArray) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").path("pos-foundry").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ testRetrieveSettings(username, null, numOfResult, metadata,
+ isMetadataArray);
+ }
+
+ private void testUpdateSetting (String username) throws KustvaktException {
+ Map<String, Object> map = new HashMap<>();
+ map.put("pos-foundry", "malt");
+ map.put("resultPerPage", 15);
+ map.put("metadata", "author title");
+ Response response = sendPutRequest(username, map);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ testRetrieveSettings(username, "malt", 15, "author title", false);
+ }
+
+ private void testRetrieveSettings (String username, String posFoundry,
+ int numOfResult, String metadata, boolean isMetadataArray)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("~" + username)
+ .path("setting").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ if (posFoundry == null) {
+ assertTrue(node.at("/pos-foundry").isMissingNode());
+ }
+ else {
+ assertEquals(posFoundry, node.at("/pos-foundry").asText());
+ }
+ assertEquals(numOfResult, node.at("/resultPerPage").asInt());
+ if (isMetadataArray) {
+ assertEquals(metadata, node.at("/metadata").toString());
+ }
+ else {
+ assertEquals(metadata, node.at("/metadata").asText());
+ }
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/VCReferenceTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/VCReferenceTest.java
new file mode 100644
index 0000000..346b71d
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/VCReferenceTest.java
@@ -0,0 +1,215 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.cache.VirtualCorpusCache;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.NamedVCLoader;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.dao.QueryDao;
+import de.ids_mannheim.korap.entity.QueryDO;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.util.QueryException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class VCReferenceTest extends SpringJerseyTest {
+
+ @Autowired
+ private NamedVCLoader vcLoader;
+
+ @Autowired
+ private QueryDao dao;
+
+ /**
+ * VC data exists, but it has not been cached, so it is not found
+ * in the DB.
+ *
+ * @throws KustvaktException
+ */
+ @Test
+ public void testRefVcNotPrecached () throws KustvaktException {
+ JsonNode node = testSearchWithRef_VC1();
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Virtual corpus system/named-vc1 is not found.");
+ assertEquals(node.at("/errors/0/2").asText(), "system/named-vc1");
+ }
+
+ @Test
+ public void testRefVcPrecached ()
+ throws KustvaktException, IOException, QueryException {
+ int numOfMatches = testSearchWithoutRef_VC1();
+ vcLoader.loadVCToCache("named-vc1", "/vc/named-vc1.jsonld");
+ assertTrue(VirtualCorpusCache.contains("named-vc1"));
+ JsonNode node = testSearchWithRef_VC1();
+ assertEquals(numOfMatches, node.at("/matches").size());
+ testStatisticsWithRef();
+ numOfMatches = testSearchWithoutRef_VC2();
+ vcLoader.loadVCToCache("named-vc2", "/vc/named-vc2.jsonld");
+ assertTrue(VirtualCorpusCache.contains("named-vc2"));
+ node = testSearchWithRef_VC2();
+ assertEquals(numOfMatches, node.at("/matches").size());
+ VirtualCorpusCache.delete("named-vc2");
+ assertFalse(VirtualCorpusCache.contains("named-vc2"));
+ QueryDO vc = dao.retrieveQueryByName("named-vc1", "system");
+ dao.deleteQuery(vc);
+ vc = dao.retrieveQueryByName("named-vc1", "system");
+ assertNull(vc);
+ vc = dao.retrieveQueryByName("named-vc2", "system");
+ dao.deleteQuery(vc);
+ vc = dao.retrieveQueryByName("named-vc2", "system");
+ assertNull(vc);
+ }
+
+ private int testSearchWithoutRef_VC1 () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq",
+ "textSigle=\"GOE/AGF/00000\" | textSigle=\"GOE/AGA/01784\"")
+ .request().get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ int size = node.at("/matches").size();
+ assertTrue(size > 0);
+ return size;
+ }
+
+ private int testSearchWithoutRef_VC2 () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq",
+ "textSigle!=\"GOE/AGI/04846\" & textSigle!=\"GOE/AGA/01784\"")
+ .request().get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ int size = node.at("/matches").size();
+ assertTrue(size > 0);
+ return size;
+ }
+
+ private JsonNode testSearchWithRef_VC1 () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"system/named-vc1\"").request()
+ .get();
+ String ent = response.readEntity(String.class);
+ return JsonUtils.readTree(ent);
+ }
+
+ private JsonNode testSearchWithRef_VC2 () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo named-vc2").request().get();
+ String ent = response.readEntity(String.class);
+ return JsonUtils.readTree(ent);
+ }
+
+ @Test
+ public void testStatisticsWithRef () throws KustvaktException {
+ String corpusQuery = "availability = /CC-BY.*/ & referTo named-vc1";
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("corpusQuery", corpusQuery).request().get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(2, node.at("/documents").asInt());
+ VirtualCorpusCache.delete("named-vc1");
+ assertFalse(VirtualCorpusCache.contains("named-vc1"));
+ }
+
+ @Test
+ public void testRefVcNotExist () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"username/vc1\"").request().get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/2").asText(), "username/vc1");
+ }
+
+ @Test
+ public void testRefNotAuthorized () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"dory/dory-vc\"").request().get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/2").asText(), "guest");
+ }
+
+ @Test
+ public void testSearchWithRefPublishedVcGuest () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"marlin/published-vc\"").request()
+ .get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertTrue(node.at("/matches").size() > 0);
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "CC-BY.*");
+ assertEquals(node.at("/collection/operands/1/@type").asText(),
+ "koral:doc");
+ assertEquals(node.at("/collection/operands/1/value").asText(), "GOE");
+ assertEquals(node.at("/collection/operands/1/key").asText(),
+ "corpusSigle");
+ node = node.at("/collection/operands/1/rewrites");
+ assertEquals(3, node.size());
+ assertEquals(node.at("/0/operation").asText(), "operation:deletion");
+ assertEquals(node.at("/0/scope").asText(), "@type(koral:docGroupRef)");
+ assertEquals(node.at("/1/operation").asText(), "operation:deletion");
+ assertEquals(node.at("/1/scope").asText(), "ref(marlin/published-vc)");
+ assertEquals(node.at("/2/operation").asText(), "operation:insertion");
+ }
+
+ @Test
+ public void testSearchWithRefPublishedVc () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"marlin/published-vc\"").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("squirt", "pass"))
+ .get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertTrue(node.at("/matches").size() > 0);
+ Form f = new Form();
+ f.param("status", "HIDDEN");
+ // check dory in the hidden group of the vc
+ response = target().path(API_VERSION).path("admin").path("group")
+ .path("list").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(f));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ assertEquals(3, node.at("/0/id").asInt());
+ String members = node.at("/0/members").toString();
+ assertTrue(members.contains("\"userId\":\"squirt\""));
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusAccessTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusAccessTest.java
new file mode 100644
index 0000000..a1594fc
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusAccessTest.java
@@ -0,0 +1,165 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.constant.ResourceType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class VirtualCorpusAccessTest extends VirtualCorpusTestBase {
+
+ private String testUser = "VirtualCorpusAccessTest";
+
+ @Test
+ public void testlistAccessByNonVCAAdmin () throws KustvaktException {
+ JsonNode node = listAccessByGroup("nemo", "dory-group");
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Unauthorized operation for user: nemo");
+ }
+
+ // @Test
+ // public void testlistAccessMissingId () throws KustvaktException
+ // {
+ // Response response =
+ // target().path(API_VERSION).path("vc")
+ // .path("access")
+ // .request().header(Attributes.AUTHORIZATION,
+ // HttpAuthorizationHandler
+ // .createBasicAuthorizationHeaderValue(
+ // testUser, "pass"))
+ // .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ // .get();
+ // String entity = response.readEntity(String.class);
+ // JsonNode node = JsonUtils.readTree(entity);
+ // assertEquals(Status.BAD_REQUEST.getStatusCode(),
+ // response.getStatus());
+ // assertEquals(StatusCodes.MISSING_PARAMETER,
+ // node.at("/errors/0/0").asInt());
+ // assertEquals("vcId", node.at("/errors/0/1").asText());
+ // }
+ @Test
+ public void testlistAccessByGroup () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("access")
+ .queryParam("groupName", "dory-group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .get();
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(1, node.at("/0/accessId").asInt());
+ assertEquals(2, node.at("/0/queryId").asInt());
+ assertEquals(node.at("/0/queryName").asText(), "group-vc");
+ assertEquals(2, node.at("/0/userGroupId").asInt());
+ assertEquals(node.at("/0/userGroupName").asText(), "dory-group");
+ }
+
+ @Test
+ public void testDeleteSharedVC () throws KustvaktException {
+ String json = "{\"type\": \"PROJECT\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"corpusSigle=GOE\"}";
+ String vcName = "new_project_vc";
+ String username = "dory";
+ String authHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass");
+ createVC(authHeader, username, vcName, json);
+ String groupName = "dory-group";
+ testShareVCByCreator(username, vcName, groupName);
+ JsonNode node = listAccessByGroup(username, groupName);
+ assertEquals(2, node.size());
+ // delete project VC
+ deleteVC(vcName, username, username);
+ node = listAccessByGroup(username, groupName);
+ assertEquals(1, node.size());
+ }
+
+ @Test
+ public void testCreateDeleteAccess ()
+ throws ProcessingException, KustvaktException {
+ String vcName = "marlin-vc";
+ String groupName = "marlin-group";
+ // check the vc type
+ JsonNode node = retrieveVCInfo("marlin", "marlin", vcName);
+ assertEquals(vcName, node.at("/name").asText());
+ assertEquals(node.at("/type").asText(), "private");
+ // share vc to group
+ Response response = testShareVCByCreator("marlin", vcName, groupName);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // check the vc type
+ node = retrieveVCInfo("marlin", "marlin", vcName);
+ assertEquals(node.at("/type").asText(), "project");
+ // list vc access by marlin
+ node = listAccessByGroup("marlin", groupName);
+ assertEquals(2, node.size());
+ // get access id
+ node = node.get(1);
+ assertEquals(5, node.at("/queryId").asInt());
+ assertEquals(vcName, node.at("/queryName").asText());
+ assertEquals(1, node.at("/userGroupId").asInt());
+ assertEquals(groupName, node.at("/userGroupName").asText());
+ String accessId = node.at("/accessId").asText();
+ testShareVC_nonUniqueAccess("marlin", vcName, groupName);
+ // delete unauthorized
+ response = testDeleteAccess(testUser, accessId);
+ testResponseUnauthorized(response, testUser);
+ // delete access by vc-admin
+ // dory is a vc-admin in marlin group
+ response = testDeleteAccess("dory", accessId);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // list vc access by dory
+ node = listAccessByGroup("dory", groupName);
+ assertEquals(1, node.size());
+ // edit VC back to private
+ String json = "{\"type\": \"" + ResourceType.PRIVATE + "\"}";
+ editVC("marlin", "marlin", vcName, json);
+ node = retrieveVCInfo("marlin", "marlin", vcName);
+ assertEquals(ResourceType.PRIVATE.displayName(),
+ node.at("/type").asText());
+ }
+
+ private void testShareVC_nonUniqueAccess (String vcCreator, String vcName,
+ String groupName) throws ProcessingException, KustvaktException {
+ Response response = testShareVCByCreator(vcCreator, vcName, groupName);
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatus());
+ assertEquals(StatusCodes.DB_INSERT_FAILED,
+ node.at("/errors/0/0").asInt());
+ // EM: message differs depending on the database used
+ // for testing. The message below is from sqlite.
+ // assertTrue(node.at("/errors/0/1").asText()
+ // .startsWith("[SQLITE_CONSTRAINT_UNIQUE]"));
+ }
+
+ private Response testDeleteAccess (String username, String accessId)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("access")
+ .path(accessId).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .delete();
+ return response;
+ }
+
+ @Test
+ public void testDeleteNonExistingAccess ()
+ throws ProcessingException, KustvaktException {
+ Response response = testDeleteAccess("dory", "100");
+ assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusClientTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusClientTest.java
new file mode 100644
index 0000000..a3b7e12
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusClientTest.java
@@ -0,0 +1,76 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class VirtualCorpusClientTest extends VirtualCorpusTestBase {
+
+ private String username = "VirtualCorpusClientTest";
+
+ @Test
+ public void testVC_withClient () throws KustvaktException {
+ // create client
+ Response response = registerConfidentialClient(username);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ String clientId = node.at("/client_id").asText();
+ String clientSecret = node.at("/client_secret").asText();
+ // obtain authorization
+ String userAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "password");
+ response = requestAuthorizationCode("code", clientId, clientRedirectUri,
+ "create_vc vc_info delete_vc edit_vc", "myState",
+ userAuthHeader);
+ String code = parseAuthorizationCode(response);
+ response = requestTokenWithAuthorizationCodeAndForm(clientId,
+ clientSecret, code, clientRedirectUri);
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ String accessToken = node.at("/access_token").asText();
+ String accessTokenHeader = "Bearer " + accessToken;
+ // create VC 1
+ String vcName = "vc-client1";
+ String vcJson = "{\"type\": \"PRIVATE\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"creationDate since 1820\"}";
+ createVC(accessTokenHeader, username, vcName, vcJson);
+ // create VC 2
+ vcName = "vc-client2";
+ vcJson = "{\"type\": \"PRIVATE\"" + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"creationDate until 1820\"}";
+ createVC(accessTokenHeader, username, vcName, vcJson);
+ // edit VC
+ String description = "vc created from client";
+ vcJson = "{\"description\": \"" + description + "\"}";
+ editVC(username, username, vcName, vcJson);
+ // retrieve vc info
+ node = retrieveVCInfo(username, username, vcName);
+ assertEquals(description, node.at("/description").asText());
+ // list vc
+ node = listVCWithAuthHeader(accessTokenHeader);
+ assertEquals(3, node.size());
+ // delete vc
+ deleteVC(vcName, username, username);
+ // list vc
+ node = listVCWithAuthHeader(accessTokenHeader);
+ assertEquals(2, node.size());
+ // delete client
+ deregisterClient(username, clientId);
+ testSearchWithRevokedAccessToken(accessToken);
+ // obtain authorization from another client
+ response = requestTokenWithPassword(superClientId, this.clientSecret,
+ username, "pass");
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ accessToken = node.at("/access_token").asText();
+ // checking vc should still be available after client deregistration
+ node = listVCWithAuthHeader("Bearer " + accessToken);
+ assertEquals(2, node.size());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerAdminTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerAdminTest.java
new file mode 100644
index 0000000..90f9d4a
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerAdminTest.java
@@ -0,0 +1,223 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.http.entity.ContentType;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.constant.ResourceType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author margaretha
+ */
+public class VirtualCorpusControllerAdminTest extends VirtualCorpusTestBase {
+
+ private String admin = "admin";
+
+ private String testUser = "VirtualCorpusControllerAdminTest";
+
+ private JsonNode testAdminListVC (String username)
+ throws ProcessingException, KustvaktException {
+ Form f = new Form();
+ f.param("createdBy", username);
+ Response response = target().path(API_VERSION).path("admin").path("vc")
+ .path("list").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(f));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+
+ private JsonNode testAdminListVC_UsingAdminToken (String username,
+ ResourceType type) throws ProcessingException, KustvaktException {
+ Form f = new Form();
+ f.param("createdBy", username);
+ f.param("type", type.toString());
+ f.param("token", "secret");
+ Response response = target().path(API_VERSION).path("admin").path("vc")
+ .path("list").request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(f));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+
+ @Test
+ public void testCreateSystemVC () throws KustvaktException {
+ String json = "{\"type\": \"SYSTEM\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"creationDate since 1820\"}";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~system").path("new-system-vc").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ JsonNode node = testAdminListVC_UsingAdminToken("system",
+ ResourceType.SYSTEM);
+ assertEquals(2, node.size());
+ testDeleteSystemVC(admin, "new-system-vc");
+ }
+
+ private void testDeleteSystemVC (String vcCreator, String vcName)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~system").path(vcName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = testAdminListVC_UsingAdminToken("system",
+ ResourceType.SYSTEM);
+ assertEquals(1, node.size());
+ }
+
+ @Test
+ public void testCreatePrivateVC ()
+ throws ProcessingException, KustvaktException {
+ String json = "{\"type\": \"PRIVATE\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"corpusSigle=GOE\"}";
+ String vcName = "new-vc";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path(vcName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ JsonNode node = testAdminListVC(testUser);
+ assertEquals(1, node.size());
+ testEditPrivateVC(testUser, vcName);
+ testDeletePrivateVC(testUser, vcName);
+ }
+
+ private void testEditPrivateVC (String vcCreator, String vcName)
+ throws ProcessingException, KustvaktException {
+ String json = "{\"description\": \"edited vc\"}";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + vcCreator).path(vcName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
+ JsonNode node = testAdminListVC(testUser);
+ assertEquals(node.at("/0/description").asText(), "edited vc");
+ }
+
+ private void testDeletePrivateVC (String vcCreator, String vcName)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + vcCreator).path(vcName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = testAdminListVC(vcCreator);
+ assertEquals(0, node.size());
+ }
+
+ // @Deprecated
+ // private String testlistAccessByVC (String groupName) throws KustvaktException {
+ // Response response = target().path(API_VERSION).path("vc")
+ // .path("access")
+ // .queryParam("groupName", groupName)
+ // .request()
+ // .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ // .createBasicAuthorizationHeaderValue(admin, "pass"))
+ // .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ // .get();
+ //
+ // String entity = response.readEntity(String.class);
+ // JsonNode node = JsonUtils.readTree(entity);
+ // assertEquals(1, node.size());
+ // node = node.get(0);
+ //
+ // assertEquals(admin, node.at("/createdBy").asText());
+ // assertEquals(5, node.at("/vcId").asInt());
+ // assertEquals("marlin-vc", node.at("/vcName").asText());
+ // assertEquals(1, node.at("/userGroupId").asInt());
+ // assertEquals("marlin group", node.at("/userGroupName").asText());
+ //
+ // return node.at("/accessId").asText();
+ // }
+ private JsonNode testlistAccessByGroup (String groupName)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("access")
+ .queryParam("groupName", groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.size());
+ return node.get(node.size() - 1);
+ }
+
+ @Test
+ public void testVCSharing () throws ProcessingException, KustvaktException {
+ String vcCreator = "marlin";
+ String vcName = "marlin-vc";
+ String groupName = "marlin-group";
+ JsonNode node2 = testAdminListVC_UsingAdminToken(vcCreator,
+ ResourceType.PROJECT);
+ assertEquals(0, node2.size());
+ testCreateVCAccess(vcCreator, vcName, groupName);
+ JsonNode node = testlistAccessByGroup(groupName);
+ String accessId = node.at("/accessId").asText();
+ testDeleteVCAccess(accessId);
+ node2 = testAdminListVC_UsingAdminToken(vcCreator,
+ ResourceType.PROJECT);
+ assertEquals(1, node2.size());
+ String json = "{\"type\": \"" + ResourceType.PRIVATE + "\"}";
+ editVC(admin, vcCreator, vcName, json);
+ node = retrieveVCInfo(admin, vcCreator, vcName);
+ assertEquals(ResourceType.PRIVATE.displayName(),
+ node.at("/type").asText());
+ node2 = testAdminListVC_UsingAdminToken(vcCreator,
+ ResourceType.PROJECT);
+ assertEquals(0, node2.size());
+ }
+
+ private void testCreateVCAccess (String vcCreator, String vcName,
+ String groupName) throws ProcessingException, KustvaktException {
+ Response response;
+ // share VC
+ response = target().path(API_VERSION).path("vc").path("~" + vcCreator)
+ .path(vcName).path("share").path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .post(Entity.form(new Form()));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ private void testDeleteVCAccess (String accessId)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("access")
+ .path(accessId).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .delete();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
new file mode 100644
index 0000000..a9ff786
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
@@ -0,0 +1,479 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.http.entity.ContentType;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.constant.AuthenticationScheme;
+import de.ids_mannheim.korap.constant.ResourceType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author margaretha
+ */
+public class VirtualCorpusControllerTest extends VirtualCorpusTestBase {
+
+ private String testUser = "vcControllerTest";
+
+ private String authHeader;
+
+ public VirtualCorpusControllerTest () throws KustvaktException {
+ authHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass");
+ }
+
+ @Test
+ public void testCreatePrivateVC () throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"corpusSigle=GOE\"}";
+ createVC(authHeader, testUser, "new_vc", json);
+ // list user VC
+ JsonNode node = listVC(testUser);
+ assertEquals(2, node.size());
+ assertEquals(node.get(1).get("name").asText(), "new_vc");
+ // delete new VC
+ deleteVC("new_vc", testUser, testUser);
+ // list VC
+ node = listVC(testUser);
+ assertEquals(1, node.size());
+ }
+
+ @Test
+ public void testCreatePublishedVC () throws KustvaktException {
+ String json = "{\"type\": \"PUBLISHED\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"corpusSigle=GOE\"}";
+ String vcName = "new-published-vc";
+ createVC(authHeader, testUser, vcName, json);
+ // test list owner vc
+ JsonNode node = retrieveVCInfo(testUser, testUser, vcName);
+ assertEquals(vcName, node.get("name").asText());
+ // EM: check hidden access
+ node = listAccessByGroup("admin", "");
+ node = node.get(node.size() - 1);
+ assertEquals(node.at("/createdBy").asText(), "system");
+ assertEquals(vcName, node.at("/queryName").asText());
+ assertTrue(node.at("/userGroupName").asText().startsWith("auto"));
+ assertEquals(vcName, node.at("/queryName").asText());
+ String groupName = node.at("/userGroupName").asText();
+ // EM: check if hidden group has been created
+ node = testCheckHiddenGroup(groupName);
+ assertEquals(node.at("/status").asText(), "HIDDEN");
+ // EM: delete vc
+ deleteVC(vcName, testUser, testUser);
+ // EM: check if the hidden groups are deleted as well
+ node = testCheckHiddenGroup(groupName);
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Group " + groupName + " is not found",
+ node.at("/errors/0/1").asText());
+ }
+
+ private JsonNode testCheckHiddenGroup (String groupName)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("admin")
+ .path("group").path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").post(null);
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+
+ @Test
+ public void testCreateVCWithInvalidToken ()
+ throws IOException, KustvaktException {
+ String json = "{\"type\": \"PRIVATE\","
+ + "\"corpusQuery\": \"corpusSigle=GOE\"}";
+ InputStream is = getClass().getClassLoader()
+ .getResourceAsStream("test-invalid-signature.token");
+ String authToken;
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(is))) {
+ authToken = reader.readLine();
+ }
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path("new_vc").request()
+ .header(Attributes.AUTHORIZATION,
+ AuthenticationScheme.BEARER.displayName() + " "
+ + authToken)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ACCESS_TOKEN,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Access token is invalid");
+ checkWWWAuthenticateHeader(response);
+ }
+
+ @Test
+ public void testCreateVCWithExpiredToken ()
+ throws IOException, KustvaktException {
+ String json = "{\"type\": \"PRIVATE\","
+ + "\"corpusQuery\": \"corpusSigle=GOE\"}";
+
+ //String authToken = "fia0123ikBWn931470H8s5gRqx7Moc4p";
+ String authToken = createExpiredAccessToken();
+
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~marlin").path("new_vc").request()
+ .header(Attributes.AUTHORIZATION,
+ AuthenticationScheme.BEARER.displayName() + " "
+ + authToken)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.EXPIRED, node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Access token is expired");
+ checkWWWAuthenticateHeader(response);
+ }
+
+ @Test
+ public void testCreateSystemVC () throws KustvaktException {
+ String json = "{\"type\": \"SYSTEM\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"pubDate since 1820\"}";
+ String vcName = "new_system_vc";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~system").path(vcName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ JsonNode node = listSystemVC("pearl");
+ assertEquals(2, node.size());
+ assertEquals(ResourceType.SYSTEM.displayName(),
+ node.at("/0/type").asText());
+ assertEquals(ResourceType.SYSTEM.displayName(),
+ node.at("/1/type").asText());
+ deleteVC(vcName, "system", "admin");
+ node = listSystemVC("pearl");
+ assertEquals(1, node.size());
+ }
+
+ @Test
+ public void testCreateSystemVC_unauthorized () throws KustvaktException {
+ String json = "{\"type\": \"SYSTEM\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"creationDate since 1820\"}";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path("new_vc").request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ testResponseUnauthorized(response, testUser);
+ }
+
+ @Test
+ public void testCreateVC_invalidName () throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"creationDate since 1820\"}";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path("new $vc").request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testCreateVC_nameTooShort () throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"creationDate since 1820\"}";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path("ne").request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "queryName must contain at least 3 characters");
+ }
+
+ @Test
+ public void testCreateVC_unauthorized () throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\","
+ + "\"corpusQuery\": \"creationDate since 1820\"}";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path("new_vc").request()
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Unauthorized operation for user: guest");
+ checkWWWAuthenticateHeader(response);
+ }
+
+ @Test
+ public void testCreateVC_withoutCorpusQuery () throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\"" + "}";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path("new_vc").request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(), "corpusQuery is null");
+ assertEquals(node.at("/errors/0/2").asText(), "corpusQuery");
+ }
+
+ @Test
+ public void testCreateVC_withoutEntity () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path("new_vc").request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(""));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(), "request entity is null");
+ assertEquals(node.at("/errors/0/2").asText(), "request entity");
+ }
+
+ @Test
+ public void testCreateVC_withoutType () throws KustvaktException {
+ String json = "{\"corpusQuery\": " + "\"creationDate since 1820\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\"" + "}";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path("new_vc").request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(), "type is null");
+ assertEquals(node.at("/errors/0/2").asText(), "type");
+ }
+
+ @Test
+ public void testCreateVC_withWrongType () throws KustvaktException {
+ String json = "{\"type\": \"PRIVAT\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"creationDate since 1820\"}";
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path("new_vc").request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.DESERIALIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertTrue(node.at("/errors/0/1").asText().startsWith(
+ "Cannot deserialize value of type `de.ids_mannheim.korap.constant."
+ + "ResourceType` from String \"PRIVAT\""));
+ }
+
+ @Test
+ public void testMaxNumberOfVC () throws KustvaktException {
+ String json = "{\"type\": \"PRIVATE\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"corpusSigle=GOE\"}";
+ for (int i = 1; i < 6; i++) {
+ createVC(authHeader, testUser, "new_vc_" + i, json);
+ }
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + testUser).path("new_vc_6").request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.NOT_ALLOWED, node.at("/errors/0/0").asInt());
+ assertEquals(
+ "Cannot create virtual corpus. The maximum number of "
+ + "virtual corpus has been reached.",
+ node.at("/errors/0/1").asText());
+ // list user VC
+ node = listVC(testUser);
+ // including 1 system-vc
+ assertEquals(6, node.size());
+ // delete new VC
+ for (int i = 1; i < 6; i++) {
+ deleteVC("new_vc_" + i, testUser, testUser);
+ }
+ // list VC
+ node = listVC(testUser);
+ // system-vc
+ assertEquals(1, node.size());
+ }
+
+ @Test
+ public void testDeleteVC_unauthorized () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("~dory")
+ .path("dory-vc").request()
+ .header(Attributes.AUTHORIZATION, authHeader).delete();
+ testResponseUnauthorized(response, testUser);
+ }
+
+ @Test
+ public void testEditVC () throws KustvaktException {
+ // 1st edit
+ String json = "{\"description\": \"edited vc\"}";
+ editVC("dory", "dory", "dory-vc", json);
+ // check VC
+ JsonNode node = retrieveVCInfo("dory", "dory", "dory-vc");
+ assertEquals(node.at("/description").asText(), "edited vc");
+ // 2nd edit
+ json = "{\"description\": \"test vc\"}";
+ editVC("dory", "dory", "dory-vc", json);
+ // check VC
+ node = retrieveVCInfo("dory", "dory", "dory-vc");
+ assertEquals(node.at("/description").asText(), "test vc");
+ }
+
+ @Test
+ public void testEditVCName () throws KustvaktException {
+ String json = "{\"name\": \"new-name\"}";
+ Response response = target().path(API_VERSION).path("vc").path("~dory")
+ .path("dory-vc").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.DESERIALIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testEditCorpusQuery ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = testRetrieveKoralQuery("dory", "dory-vc");
+ assertEquals(node.at("/collection/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/collection/operation").asText(),
+ "operation:and");
+ assertEquals(2, node.at("/collection/operands").size());
+ String json = "{\"corpusQuery\": \"corpusSigle=WPD17\"}";
+ editVC("dory", "dory", "dory-vc", json);
+ node = testRetrieveKoralQuery("dory", "dory-vc");
+ assertEquals(node.at("/collection/@type").asText(), "koral:doc");
+ assertEquals(node.at("/collection/key").asText(), "corpusSigle");
+ assertEquals(node.at("/collection/value").asText(), "WPD17");
+ }
+
+ private JsonNode testRetrieveKoralQuery (String username, String vcName)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .path("koralQuery").path("~" + username).path(vcName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ return node;
+ }
+
+ @Test
+ public void testEditVC_notOwner () throws KustvaktException {
+ String json = "{\"description\": \"edited vc\"}";
+ Response response = target().path(API_VERSION).path("vc").path("~dory")
+ .path("dory-vc").request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(json));
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Unauthorized operation for user: " + testUser,
+ node.at("/errors/0/1").asText());
+ checkWWWAuthenticateHeader(response);
+ }
+
+ @Test
+ public void testPublishProjectVC () throws KustvaktException {
+ String vcName = "group-vc";
+ // check the vc type
+ JsonNode node = retrieveVCInfo("dory", "dory", vcName);
+ assertEquals(ResourceType.PROJECT.displayName(),
+ node.get("type").asText());
+ // edit vc
+ String json = "{\"type\": \"PUBLISHED\"}";
+ editVC("dory", "dory", vcName, json);
+ // check VC
+ node = testListOwnerVC("dory");
+ JsonNode n = node.get(1);
+ assertEquals(ResourceType.PUBLISHED.displayName(),
+ n.get("type").asText());
+ // check hidden VC access
+ node = listAccessByGroup("admin", "");
+ assertEquals(4, node.size());
+ node = node.get(node.size() - 1);
+ assertEquals(vcName, node.at("/queryName").asText());
+ assertEquals(node.at("/createdBy").asText(), "system");
+ assertTrue(node.at("/userGroupName").asText().startsWith("auto"));
+ // edit 2nd
+ json = "{\"type\": \"PROJECT\"}";
+ editVC("dory", "dory", vcName, json);
+ node = testListOwnerVC("dory");
+ assertEquals(ResourceType.PROJECT.displayName(),
+ node.get(1).get("type").asText());
+ // check VC access
+ node = listAccessByGroup("admin", "");
+ assertEquals(3, node.size());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusFieldTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusFieldTest.java
new file mode 100644
index 0000000..27c527d
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusFieldTest.java
@@ -0,0 +1,142 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+
+import org.apache.http.entity.ContentType;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.cache.VirtualCorpusCache;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.NamedVCLoader;
+import de.ids_mannheim.korap.dao.QueryDao;
+import de.ids_mannheim.korap.entity.QueryDO;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.util.QueryException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class VirtualCorpusFieldTest extends VirtualCorpusTestBase {
+
+ @Autowired
+ private NamedVCLoader vcLoader;
+
+ @Autowired
+ private QueryDao dao;
+
+ private JsonNode testRetrieveField (String username, String vcName,
+ String field) throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("field")
+ .path("~" + username).path(vcName)
+ .queryParam("fieldName", field).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ return node;
+ }
+
+ private void testRetrieveProhibitedField (String username, String vcName,
+ String field) throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("field")
+ .path("~" + username).path(vcName)
+ .queryParam("fieldName", field).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .get();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.NOT_ALLOWED, node.at("/errors/0/0").asInt());
+ }
+
+ private void deleteVcFromDB (String vcName) throws KustvaktException {
+ QueryDO vc = dao.retrieveQueryByName(vcName, "system");
+ dao.deleteQuery(vc);
+ vc = dao.retrieveQueryByName(vcName, "system");
+ assertEquals(null, vc);
+ }
+
+ @Test
+ public void testRetrieveFieldsNamedVC1 ()
+ throws IOException, QueryException, KustvaktException {
+ vcLoader.loadVCToCache("named-vc1", "/vc/named-vc1.jsonld");
+ JsonNode n = testRetrieveField("system", "named-vc1", "textSigle");
+ assertEquals(n.at("/@context").asText(),
+ "http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld");
+ assertEquals(n.at("/corpus/key").asText(), "textSigle");
+ assertEquals(2, n.at("/corpus/value").size());
+ n = testRetrieveField("system", "named-vc1", "author");
+ assertEquals(2, n.at("/corpus/value").size());
+ assertEquals(n.at("/corpus/value/0").asText(),
+ "Goethe, Johann Wolfgang von");
+ testRetrieveUnknownTokens();
+ testRetrieveProhibitedField("system", "named-vc1", "tokens");
+ testRetrieveProhibitedField("system", "named-vc1", "base");
+ VirtualCorpusCache.delete("named-vc1");
+ deleteVcFromDB("named-vc1");
+ }
+
+ private void testRetrieveUnknownTokens ()
+ throws ProcessingException, KustvaktException {
+ JsonNode n = testRetrieveField("system", "named-vc1", "unknown");
+ assertEquals(n.at("/corpus/key").asText(), "unknown");
+ assertEquals(0, n.at("/corpus/value").size());
+ }
+
+ @Test
+ public void testRetrieveTextSigleNamedVC2 ()
+ throws IOException, QueryException, KustvaktException {
+ vcLoader.loadVCToCache("named-vc2", "/vc/named-vc2.jsonld");
+ JsonNode n = testRetrieveField("system", "named-vc2", "textSigle");
+ assertEquals(2, n.at("/corpus/value").size());
+ VirtualCorpusCache.delete("named-vc2");
+ deleteVcFromDB("named-vc2");
+ }
+
+ @Test
+ public void testRetrieveTextSigleNamedVC3 ()
+ throws IOException, QueryException, KustvaktException {
+ vcLoader.loadVCToCache("named-vc3", "/vc/named-vc3.jsonld");
+ JsonNode n = testRetrieveField("system", "named-vc3", "textSigle");
+ n = n.at("/corpus/value");
+ assertEquals(1, n.size());
+ assertEquals(n.get(0).asText(), "GOE/AGI/00000");
+ VirtualCorpusCache.delete("named-vc3");
+ deleteVcFromDB("named-vc3");
+ }
+
+ @Test
+ public void testRetrieveFieldUnauthorized ()
+ throws KustvaktException, IOException, QueryException {
+ vcLoader.loadVCToCache("named-vc3", "/vc/named-vc3.jsonld");
+ Response response = target().path(API_VERSION).path("vc").path("field")
+ .path("~system").path("named-vc3")
+ .queryParam("fieldName", "textSigle").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .get();
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Unauthorized operation for user: dory");
+ VirtualCorpusCache.delete("named-vc3");
+ deleteVcFromDB("named-vc3");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusInfoTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusInfoTest.java
new file mode 100644
index 0000000..4d20c0f
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusInfoTest.java
@@ -0,0 +1,157 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.constant.ResourceType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class VirtualCorpusInfoTest extends VirtualCorpusTestBase {
+
+ private String admin = "admin";
+
+ private String testUser = "VirtualCorpusInfoTest";
+
+ @Test
+ public void testRetrieveSystemVC ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = retrieveVCInfo(testUser, "system", "system-vc");
+ assertEquals(node.at("/name").asText(), "system-vc");
+ assertEquals(ResourceType.SYSTEM.displayName(),
+ node.at("/type").asText());
+ // assertEquals("koral:doc", node.at("/koralQuery/collection/@type").asText());
+ assertTrue(node.at("/query").isMissingNode());
+ assertTrue(node.at("/queryLanguage").isMissingNode());
+ }
+
+ @Test
+ public void testRetrieveSystemVCGuest ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~system").path("system-vc").request().get();
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(node.at("/name").asText(), "system-vc");
+ assertEquals(ResourceType.SYSTEM.displayName(),
+ node.at("/type").asText());
+ }
+
+ @Test
+ public void testRetrieveOwnerPrivateVC ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = retrieveVCInfo("dory", "dory", "dory-vc");
+ assertEquals(node.at("/name").asText(), "dory-vc");
+ assertEquals(ResourceType.PRIVATE.displayName(),
+ node.at("/type").asText());
+ }
+
+ @Test
+ public void testRetrievePrivateVCUnauthorized ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("~dory")
+ .path("dory-vc").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .get();
+ testResponseUnauthorized(response, testUser);
+ }
+
+ @Test
+ public void testRetrieveProjectVC ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = retrieveVCInfo("nemo", "dory", "group-vc");
+ assertEquals(node.at("/name").asText(), "group-vc");
+ assertEquals(ResourceType.PROJECT.displayName(),
+ node.at("/type").asText());
+ }
+
+ @Test
+ public void testRetrieveProjectVCUnauthorized ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("~dory")
+ .path("group-vc").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass"))
+ .get();
+ testResponseUnauthorized(response, testUser);
+ }
+
+ @Test
+ public void testRetrieveProjectVCbyNonActiveMember ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("~dory")
+ .path("group-vc").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("marlin", "pass"))
+ .get();
+ testResponseUnauthorized(response, "marlin");
+ }
+
+ @Test
+ public void testRetrievePublishedVC ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = retrieveVCInfo("gill", "marlin", "published-vc");
+ assertEquals(node.at("/name").asText(), "published-vc");
+ assertEquals(ResourceType.PUBLISHED.displayName(),
+ node.at("/type").asText());
+ Form f = new Form();
+ f.param("status", "HIDDEN");
+ // check gill in the hidden group of the vc
+ Response response = target().path(API_VERSION).path("admin")
+ .path("group").path("list").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(f));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ assertEquals(3, node.at("/0/id").asInt());
+ String members = node.at("/0/members").toString();
+ assertTrue(members.contains("\"userId\":\"gill\""));
+ }
+
+ @Test
+ public void testAdminRetrievePrivateVC ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("~dory")
+ .path("dory-vc").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(1, node.at("/id").asInt());
+ assertEquals(node.at("/name").asText(), "dory-vc");
+ }
+
+ @Test
+ public void testAdminRetrieveProjectVC ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("~dory")
+ .path("group-vc").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(admin, "pass"))
+ .get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(node.at("/name").asText(), "group-vc");
+ assertEquals(ResourceType.PROJECT.displayName(),
+ node.at("/type").asText());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusListTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusListTest.java
new file mode 100644
index 0000000..f1e1d17
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusListTest.java
@@ -0,0 +1,93 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class VirtualCorpusListTest extends VirtualCorpusTestBase {
+
+ @Test
+ public void testListVCNemo ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = testListOwnerVC("nemo");
+ assertEquals(1, node.size());
+ node = listSystemVC("nemo");
+ assertEquals(1, node.size());
+ node = listVC("nemo");
+ assertEquals(3, node.size());
+ }
+
+ @Test
+ public void testListVCPearl ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = testListOwnerVC("pearl");
+ assertEquals(0, node.size());
+ node = listVC("pearl");
+ assertEquals(2, node.size());
+ }
+
+ @Test
+ public void testListVCDory ()
+ throws ProcessingException, KustvaktException {
+ JsonNode node = testListOwnerVC("dory");
+ assertEquals(2, node.size());
+ node = listVC("dory");
+ assertEquals(4, node.size());
+ }
+
+ @Test
+ public void testListAvailableVCGuest ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").request()
+ .get();
+ testResponseUnauthorized(response, "guest");
+ }
+
+ @Disabled
+ @Deprecated
+ @Test
+ public void testListAvailableVCByOtherUser ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("~dory")
+ .request().header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("pearl", "pass"))
+ .get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Unauthorized operation for user: pearl");
+ checkWWWAuthenticateHeader(response);
+ }
+
+ @Disabled
+ @Deprecated
+ @Test
+ public void testListUserVC ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .queryParam("username", "dory").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "pass"))
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(4, node.size());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusSharingTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusSharingTest.java
new file mode 100644
index 0000000..d7de5a3
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusSharingTest.java
@@ -0,0 +1,202 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.constant.GroupMemberStatus;
+import de.ids_mannheim.korap.constant.PredefinedRole;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class VirtualCorpusSharingTest extends VirtualCorpusTestBase {
+
+ private String testUser = "VirtualCorpusSharingTest";
+
+ @Test
+ public void testShareUnknownVC ()
+ throws ProcessingException, KustvaktException {
+ Response response = testShareVCByCreator("marlin", "non-existing-vc",
+ "marlin group");
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatus());
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testShareUnknownGroup ()
+ throws ProcessingException, KustvaktException {
+ Response response = testShareVCByCreator("marlin", "marlin-vc",
+ "non-existing-group");
+ JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatus());
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testShareVC_notOwner ()
+ throws ProcessingException, KustvaktException {
+ // dory is VCA in marlin group
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~marlin").path("marlin-vc").path("share")
+ .path("@marlin group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "pass"))
+ .post(Entity.form(new Form()));
+ testResponseUnauthorized(response, "dory");
+ }
+
+ @Test
+ public void testShareVC_byMember ()
+ throws ProcessingException, KustvaktException {
+ // nemo is not VCA in marlin group
+ Response response = target().path(API_VERSION).path("vc").path("~nemo")
+ .path("nemo-vc").path("share").path("@marlin-group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("nemo", "pass"))
+ .post(Entity.form(new Form()));
+ testResponseUnauthorized(response, "nemo");
+ }
+
+ @Test
+ public void testCreateShareProjectVC () throws KustvaktException {
+ String json = "{\"type\": \"PROJECT\""
+ + ",\"queryType\": \"VIRTUAL_CORPUS\""
+ + ",\"corpusQuery\": \"corpusSigle=GOE\"}";
+ String vcName = "new_project_vc";
+ String authHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(testUser, "pass");
+ createVC(authHeader, testUser, vcName, json);
+ // retrieve vc info
+ JsonNode vcInfo = retrieveVCInfo(testUser, testUser, vcName);
+ assertEquals(vcName, vcInfo.get("name").asText());
+ // list user VC
+ JsonNode node = listVC(testUser);
+ assertEquals(2, node.size());
+ assertEquals(vcName, node.get(1).get("name").asText());
+ // search by non member
+ Response response = searchWithVCRef("dory", testUser, vcName);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ // create user group
+ String groupName = "owidGroup";
+ String memberName = "darla";
+ response = createUserGroup(testUser, groupName, "Owid users");
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ listUserGroup(testUser, groupName);
+ testInviteMember(testUser, groupName, "darla");
+ subscribeToGroup(memberName, groupName);
+ checkMemberInGroup(memberName, testUser, groupName);
+ // share vc to group
+ testShareVCByCreator(testUser, vcName, groupName);
+ node = listAccessByGroup(testUser, groupName);
+ // search by member
+ response = searchWithVCRef(memberName, testUser, vcName);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ assertTrue(node.at("/matches").size() > 0);
+ // delete project VC
+ deleteVC(vcName, testUser, testUser);
+ // list VC
+ node = listVC(testUser);
+ assertEquals(1, node.size());
+ // search by member
+ response = searchWithVCRef(memberName, testUser, vcName);
+ assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.readEntity(String.class));
+ assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+ node.at("/errors/0/0").asInt());
+ }
+
+ private Response createUserGroup (String username, String groupName,
+ String description) throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("description", description);
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .put(Entity.form(form));
+ return response;
+ }
+
+ private JsonNode listUserGroup (String username, String groupName)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ return node;
+ }
+
+ private void testInviteMember (String username, String groupName,
+ String memberName) throws ProcessingException, KustvaktException {
+ Form form = new Form();
+ form.param("members", memberName);
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("invite").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .post(Entity.form(form));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ // list group
+ JsonNode node = listUserGroup(username, groupName);
+ node = node.get(0);
+ assertEquals(2, node.get("members").size());
+ assertEquals(memberName, node.at("/members/1/userId").asText());
+ assertEquals(GroupMemberStatus.PENDING.name(),
+ node.at("/members/1/status").asText());
+ assertEquals(0, node.at("/members/1/roles").size());
+ }
+
+ private void subscribeToGroup (String username, String groupName)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("group")
+ .path("@" + groupName).path("subscribe").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .post(Entity.form(new Form()));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ private void checkMemberInGroup (String memberName, String testUser,
+ String groupName) throws KustvaktException {
+ JsonNode node = listUserGroup(testUser, groupName).get(0);
+ assertEquals(2, node.get("members").size());
+ assertEquals(memberName, node.at("/members/1/userId").asText());
+ assertEquals(GroupMemberStatus.ACTIVE.name(),
+ node.at("/members/1/status").asText());
+ assertEquals(PredefinedRole.VC_ACCESS_MEMBER.name(),
+ node.at("/members/1/roles/1").asText());
+ assertEquals(PredefinedRole.USER_GROUP_MEMBER.name(),
+ node.at("/members/1/roles/0").asText());
+ }
+
+ private Response searchWithVCRef (String username, String vcCreator,
+ String vcName) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq",
+ "referTo \"" + vcCreator + "/" + vcName + "\"")
+ .request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .get();
+ return response;
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusTestBase.java b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusTestBase.java
new file mode 100644
index 0000000..04538e9
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusTestBase.java
@@ -0,0 +1,180 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.apache.http.entity.ContentType;
+import org.glassfish.jersey.server.ContainerRequest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public abstract class VirtualCorpusTestBase extends OAuth2TestBase {
+
+ protected JsonNode retrieveVCInfo (String username, String vcCreator,
+ String vcName) throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + vcCreator).path(vcName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .get();
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+ return JsonUtils.readTree(entity);
+ }
+
+ protected void createVC (String authHeader, String username, String vcName,
+ String vcJson) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + username).path(vcName).request()
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(vcJson));
+
+ assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+ }
+
+ protected void editVC (String username, String vcCreator, String vcName,
+ String vcJson) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + vcCreator).path(vcName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .put(Entity.json(vcJson));
+
+ assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
+ }
+
+ protected JsonNode listVC (String username)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .get();
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ // System.out.println(entity);
+ return JsonUtils.readTree(entity);
+ }
+
+ protected JsonNode listVCWithAuthHeader (String authHeader)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").request()
+ .header(Attributes.AUTHORIZATION, authHeader).get();
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+
+ protected JsonNode testListOwnerVC (String username)
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .queryParam("filter-by", "own").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .get();
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+ String entity = response.readEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
+
+ protected JsonNode listSystemVC (String username) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .queryParam("filter-by", "system").request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("pearl", "pass"))
+ .get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ return node;
+ }
+
+ protected Response testShareVCByCreator (String vcCreator, String vcName,
+ String groupName) throws ProcessingException, KustvaktException {
+
+ return target().path(API_VERSION).path("vc").path("~" + vcCreator)
+ .path(vcName).path("share").path("@" + groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(vcCreator, "pass"))
+ .post(Entity.form(new Form()));
+ }
+
+ protected JsonNode listAccessByGroup (String username, String groupName)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc").path("access")
+ .queryParam("groupName", groupName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ return node;
+ }
+
+ protected void deleteVC (String vcName, String vcCreator, String username)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("vc")
+ .path("~" + vcCreator).path(vcName).request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .delete();
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ protected void testResponseUnauthorized (Response response, String username)
+ throws KustvaktException {
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Unauthorized operation for user: " + username,
+ node.at("/errors/0/1").asText());
+
+ checkWWWAuthenticateHeader(response);
+ }
+
+ protected void checkWWWAuthenticateHeader (Response response) {
+ Set<Entry<String, List<Object>>> headers = response.getHeaders()
+ .entrySet();
+
+ for (Entry<String, List<Object>> header : headers) {
+ if (header.getKey().equals(ContainerRequest.WWW_AUTHENTICATE)) {
+ assertThat(header.getValue(),
+ not(hasItem("Api realm=\"Kustvakt\"")));
+ assertThat(header.getValue(),
+ hasItem("Bearer realm=\"Kustvakt\""));
+ assertThat(header.getValue(),
+ hasItem("Basic realm=\"Kustvakt\""));
+ }
+ }
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/lite/InfoControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/lite/InfoControllerTest.java
new file mode 100644
index 0000000..27054fd
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/lite/InfoControllerTest.java
@@ -0,0 +1,45 @@
+package de.ids_mannheim.korap.web.lite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.LiteJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.ServiceInfo;
+import de.ids_mannheim.korap.web.SearchKrill;
+
+public class InfoControllerTest extends LiteJerseyTest {
+
+ @Autowired
+ private KustvaktConfiguration config;
+
+ @Autowired
+ private SearchKrill krill;
+
+ @Test
+ public void testInfo () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("info").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(config.getCurrentVersion(),
+ node.at("/latest_api_version").asText());
+ assertEquals(config.getSupportedVersions().size(),
+ node.at("/supported_api_versions").size());
+ assertEquals(ServiceInfo.getInfo().getVersion(),
+ node.at("/kustvakt_version").asText());
+ assertEquals(krill.getIndex().getVersion(),
+ node.at("/krill_version").asText());
+ assertEquals(ServiceInfo.getInfo().getKoralVersion(),
+ node.at("/koral_version").asText());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/lite/LiteMultipleCorpusQueryTest.java b/src/test/java/de/ids_mannheim/korap/web/lite/LiteMultipleCorpusQueryTest.java
new file mode 100644
index 0000000..5d93355
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/lite/LiteMultipleCorpusQueryTest.java
@@ -0,0 +1,76 @@
+package de.ids_mannheim.korap.web.lite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.config.LiteJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class LiteMultipleCorpusQueryTest extends LiteJerseyTest {
+
+ @Test
+ public void testSearchGet () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "das").queryParam("ql", "poliqarp")
+ .queryParam("cq", "pubPlace=München")
+ .queryParam("cq", "textSigle=\"GOE/AGA/01784\"").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ node = node.at("/collection");
+ assertEquals(node.at("/@type").asText(), "koral:docGroup");
+ assertEquals(node.at("/operation").asText(), "operation:and");
+ assertEquals(2, node.at("/operands").size());
+ assertEquals(node.at("/operands/0/@type").asText(), "koral:doc");
+ assertEquals(node.at("/operands/0/match").asText(), "match:eq");
+ assertEquals(node.at("/operands/0/key").asText(), "pubPlace");
+ assertEquals(node.at("/operands/0/value").asText(), "München");
+ assertEquals(node.at("/operands/1/key").asText(), "textSigle");
+ assertEquals(node.at("/operands/1/value").asText(), "GOE/AGA/01784");
+ }
+
+ @Test
+ public void testStatisticsWithMultipleCq ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("cq", "textType=Abhandlung")
+ .queryParam("cq", "corpusSigle=GOE").request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/documents").asInt());
+ assertEquals(138180, node.at("/tokens").asInt());
+ assertEquals(5687, node.at("/sentences").asInt());
+ assertEquals(258, node.at("/paragraphs").asInt());
+ assertTrue(node.at("/warnings").isMissingNode());
+ }
+
+ @Test
+ public void testStatisticsWithMultipleCorpusQuery ()
+ throws ProcessingException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("corpusQuery", "textType=Autobiographie")
+ .queryParam("corpusQuery", "corpusSigle=GOE").request()
+ .method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(9, node.at("/documents").asInt());
+ assertEquals(527662, node.at("/tokens").asInt());
+ assertEquals(19387, node.at("/sentences").asInt());
+ assertEquals(514, node.at("/paragraphs").asInt());
+ assertEquals(StatusCodes.DEPRECATED, node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/1").asText(),
+ "Parameter corpusQuery is deprecated in favor of cq.");
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/lite/LiteSearchControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/lite/LiteSearchControllerTest.java
new file mode 100644
index 0000000..610646c0
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/lite/LiteSearchControllerTest.java
@@ -0,0 +1,495 @@
+package de.ids_mannheim.korap.web.lite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Iterator;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.LiteJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.SearchKrill;
+
+public class LiteSearchControllerTest extends LiteJerseyTest {
+
+ @Autowired
+ private SearchKrill searchKrill;
+
+ @Autowired
+ private KustvaktConfiguration config;
+
+ // EM: The API is disabled
+ @Disabled
+ @Test
+ public void testGetJSONQuery () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("query")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertNotNull(node);
+ assertEquals(node.at("/query/wrap/layer").asText(), "orth");
+ assertEquals(node.at("/query/wrap/foundry").asText(), "opennlp");
+ assertEquals(node.at("/meta/context").asText(), "sentence");
+ assertEquals(node.at("/meta/count").asText(), "13");
+ }
+
+ // EM: The API is disabled
+ @Disabled
+ @Test
+ public void testbuildAndPostQuery () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("query")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "corpusSigle=WPD | corpusSigle=GOE").request()
+ .method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertNotNull(node);
+ response = target().path(API_VERSION).path("search").request()
+ .post(Entity.json(query));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String matches = response.readEntity(String.class);
+ JsonNode match_node = JsonUtils.readTree(matches);
+ assertNotEquals(0, match_node.path("matches").size());
+ }
+
+ @Test
+ public void testApiWelcomeMessage () {
+ Response response = target().path(API_VERSION).path("").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String message = response.readEntity(String.class);
+ assertEquals(response.getHeaders().getFirst("X-Index-Revision"),
+ "Wes8Bd4h1OypPqbWF5njeQ==");
+ assertEquals(message, config.getApiWelcomeMessage());
+ }
+
+ @Test
+ public void testQueryGet () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertNotNull(node);
+ assertEquals(node.at("/query/wrap/layer").asText(), "orth");
+ assertEquals(node.at("/meta/context").asText(), "base/s:s");
+ assertEquals(node.at("/meta/count").asText(), "13");
+ assertNotEquals(0, node.at("/matches").size());
+ }
+
+ @Test
+ public void testQueryFailure () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das").queryParam("ql", "poliqarp")
+ .queryParam("cq", "corpusSigle=WPD | corpusSigle=GOE")
+ .queryParam("count", "13").request().get();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertNotNull(node);
+ assertEquals(302, node.at("/errors/0/0").asInt());
+ assertEquals(302, node.at("/errors/1/0").asInt());
+ assertTrue(node.at("/errors/2").isMissingNode());
+ assertFalse(node.at("/collection").isMissingNode());
+ assertEquals(13, node.at("/meta/count").asInt());
+ }
+
+ @Test
+ public void testFoundryRewrite () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertNotNull(node);
+ assertEquals(node.at("/query/wrap/layer").asText(), "orth");
+ assertEquals(node.at("/query/wrap/foundry").asText(), "opennlp");
+ }
+
+ // EM: The API is disabled
+ @Test
+ @Disabled
+ public void testQueryPost () throws KustvaktException {
+ QuerySerializer s = new QuerySerializer();
+ s.setQuery("[orth=das]", "poliqarp");
+ Response response = target().path(API_VERSION).path("search").request()
+ .post(Entity.json(s.toJSON()));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertNotNull(node);
+ assertEquals(node.at("/query/wrap/layer").asText(), "orth");
+ assertNotEquals(0, node.at("/matches").size());
+ }
+
+ @Test
+ public void testParameterField () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("fields", "author,docSigle")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertNotNull(node);
+ assertEquals(node.at("/query/wrap/layer").asText(), "orth");
+ assertNotEquals(0, node.at("/matches").size());
+ assertEquals(node.at("/meta/fields").toString(),
+ "[\"author\",\"docSigle\"]");
+ }
+
+ @Test
+ public void testMatchInfoGetWithoutSpans () throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("corpus/GOE/AGA/01784/p36-46(5)37-45(2)38-42/matchInfo")
+ .queryParam("foundry", "*").queryParam("spans", "false")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(node.at("/textSigle").asText(), "GOE/AGA/01784");
+ assertEquals(node.at("/matchID").asText(),
+ "match-GOE/AGA/01784-p36-46(5)37-45(2)38-42");
+ assertEquals(node.at("/title").asText(), "Belagerung von Mainz");
+ }
+
+ @Test
+ public void testMatchInfoGetWithoutHighlights () throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("corpus/GOE/AGA/01784/p36-46(5)37-45(2)38-42/matchInfo")
+ .queryParam("foundry", "xy").queryParam("spans", "false")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(node.at("/snippet").asText(),
+ "<span class=\"context-left\"></span><span class=\"match\">der alte freie Weg nach Mainz war gesperrt, ich mußte über die Schiffbrücke bei Rüsselsheim; in Ginsheim ward <mark>gefüttert; der Ort ist sehr zerschossen; dann über die Schiffbrücke</mark> auf die Nonnenaue, wo viele Bäume niedergehauen lagen, sofort auf dem zweiten Teil der Schiffbrücke über den größern Arm des Rheins.</span><span class=\"context-right\"></span>");
+ assertEquals(node.at("/textSigle").asText(), "GOE/AGA/01784");
+ assertEquals(node.at("/matchID").asText(),
+ "match-GOE/AGA/01784-p36-46(5)37-45(2)38-42");
+ assertEquals(node.at("/title").asText(), "Belagerung von Mainz");
+ }
+
+ @Test
+ public void testMatchInfoWithoutExtension () throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("corpus/GOE/AGA/01784/p36-46(5)37-45(2)38-42")
+ .queryParam("foundry", "-").queryParam("spans", "false")
+ .queryParam("expand", "false").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(node.at("/textSigle").asText(), "GOE/AGA/01784");
+ assertEquals(node.at("/matchID").asText(),
+ "match-GOE/AGA/01784-p36-46(5)37-45(2)38-42");
+ assertEquals(node.at("/snippet").asText(),
+ "<span class=\"context-left\"><span class=\"more\"></span></span><span class=\"match\"><mark>gefüttert; der Ort ist sehr zerschossen; dann über die Schiffbrücke</mark></span><span class=\"context-right\"><span class=\"more\"></span></span>");
+ assertEquals(node.at("/title").asText(), "Belagerung von Mainz");
+ }
+
+ @Test
+ public void testMatchInfoGetWithHighlights () throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("corpus/GOE/AGA/01784/p36-46(5)37-45(2)38-42/matchInfo")
+ .queryParam("foundry", "xy").queryParam("spans", "false")
+ .queryParam("hls", "true").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(node.at("/textSigle").asText(), "GOE/AGA/01784");
+ assertEquals(
+ "<span class=\"context-left\"></span><span class=\"match\">"
+ + "der alte freie Weg nach Mainz war gesperrt, ich mußte über die "
+ + "Schiffbrücke bei Rüsselsheim; in Ginsheim ward <mark>gefüttert; "
+ + "<mark class=\"class-5 level-0\">der <mark class=\"class-2 level-1\">"
+ + "Ort ist sehr zerschossen; dann</mark> über die Schiffbrücke</mark></mark> "
+ + "auf die Nonnenaue, wo viele Bäume niedergehauen lagen, sofort auf dem "
+ + "zweiten Teil der Schiffbrücke über den größern Arm des Rheins.</span>"
+ + "<span class=\"context-right\"></span>",
+ node.at("/snippet").asText());
+ assertEquals(node.at("/matchID").asText(),
+ "match-GOE/AGA/01784-p36-46(5)37-45(2)38-42");
+ assertEquals(node.at("/title").asText(), "Belagerung von Mainz");
+ }
+
+ @Test
+ public void testMatchInfoGet2 () throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("corpus/GOE/AGA/01784/p36-46/matchInfo")
+ .queryParam("foundry", "*").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertNotNull(node);
+ assertEquals(node.at("/textSigle").asText(), "GOE/AGA/01784");
+ assertEquals(node.at("/title").asText(), "Belagerung von Mainz");
+ }
+
+ // EM: The API is disabled
+ @Disabled
+ @Test
+ public void testCollectionQueryParameter () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("query")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("fields", "author, docSigle")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .queryParam("cq", "textClass=Politik & corpus=WPD").request()
+ .method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertNotNull(node);
+ assertEquals(node.at("/query/wrap/layer").asText(), "orth");
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "Politik");
+ assertEquals(node.at("/collection/operands/1/value").asText(), "WPD");
+ response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("fields", "author, docSigle")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .queryParam("cq", "textClass=Politik & corpus=WPD").request()
+ .get();
+ // String version =
+ // LucenePackage.get().getImplementationVersion();;
+ // System.out.println("VERSION "+ version);
+ // System.out.println("RESPONSE "+ response);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ query = response.readEntity(String.class);
+ node = JsonUtils.readTree(query);
+ assertNotNull(node);
+ assertEquals(node.at("/query/wrap/layer").asText(), "orth");
+ assertEquals(node.at("/collection/operands/0/value").asText(),
+ "Politik");
+ assertEquals(node.at("/collection/operands/1/value").asText(), "WPD");
+ }
+
+ @Test
+ public void testTokenRetrieval () throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("/corpus/GOE/AGA/01784/p104-105/").request()
+ .method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String resp = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(resp);
+ assertTrue(node.at("/hasSnippet").asBoolean());
+ assertFalse(node.at("/hasTokens").asBoolean());
+ assertTrue(node.at("/tokens").isMissingNode());
+ assertEquals(
+ "<span class=\"context-left\"><span class=\"more\"></span></span>"
+ + "<span class=\"match\"><mark>die</mark></span>"
+ + "<span class=\"context-right\"><span class=\"more\"></span></span>",
+ node.at("/snippet").asText());
+ // Tokens
+ response = target().path(API_VERSION)
+ .path("/corpus/GOE/AGA/01784/p104-105")
+ .queryParam("show-snippet", "false")
+ .queryParam("show-tokens", "true").queryParam("expand", "false")
+ .request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ resp = response.readEntity(String.class);
+ node = JsonUtils.readTree(resp);
+ assertFalse(node.at("/hasSnippet").asBoolean());
+ assertTrue(node.at("/hasTokens").asBoolean());
+ assertTrue(node.at("/snippet").isMissingNode());
+ assertEquals(node.at("/tokens/match/0").asText(), "die");
+ assertTrue(node.at("/tokens/match/1").isMissingNode());
+ }
+
+ @Test
+ public void testMetaFields () throws KustvaktException {
+ Response response = target().path(API_VERSION)
+ .path("/corpus/GOE/AGA/01784").request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String resp = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(resp);
+ // System.err.println(node.toString());
+ Iterator<JsonNode> fieldIter = node.at("/document/fields").elements();
+ int checkC = 0;
+ while (fieldIter.hasNext()) {
+ JsonNode field = (JsonNode) fieldIter.next();
+ String key = field.at("/key").asText();
+ assertEquals(field.at("/@type").asText(), "koral:field");
+ switch (key) {
+ case "textSigle":
+ assertEquals(field.at("/type").asText(), "type:string");
+ assertEquals(field.at("/value").asText(), "GOE/AGA/01784");
+ checkC++;
+ break;
+ case "author":
+ assertEquals(field.at("/type").asText(), "type:text");
+ assertEquals(field.at("/value").asText(),
+ "Goethe, Johann Wolfgang von");
+ checkC++;
+ break;
+ case "docSigle":
+ assertEquals(field.at("/type").asText(), "type:string");
+ assertEquals(field.at("/value").asText(), "GOE/AGA");
+ checkC++;
+ break;
+ case "docTitle":
+ assertEquals(field.at("/type").asText(), "type:text");
+ assertEquals(field.at("/value").asText(),
+ "Goethe: Autobiographische Schriften II, (1817-1825, 1832)");
+ checkC++;
+ break;
+ case "pubDate":
+ assertEquals(field.at("/type").asText(), "type:date");
+ assertEquals(1982, field.at("/value").asInt());
+ checkC++;
+ break;
+ };
+ };
+ assertEquals(5, checkC);
+ }
+
+ @Test
+ public void testSearchWithoutVersion () throws KustvaktException {
+ Response response = target().path("api").path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .request().accept(MediaType.APPLICATION_JSON).get();
+ assertEquals(HttpStatus.PERMANENT_REDIRECT_308, response.getStatus());
+ URI location = response.getLocation();
+ assertEquals(location.getPath(), "/api/v1.0/search");
+ }
+
+ @Test
+ public void testSearchWrongVersion () throws KustvaktException {
+ Response response = target().path("api").path("v0.2").path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .request().accept(MediaType.APPLICATION_JSON).get();
+ assertEquals(HttpStatus.PERMANENT_REDIRECT_308, response.getStatus());
+ URI location = response.getLocation();
+ assertEquals(location.getPath(), "/api/v1.0/search");
+ }
+
+ @Test
+ public void testSearchWithIP () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Wasser").queryParam("ql", "poliqarp")
+ .request().header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertTrue(node.at("/collection").isMissingNode());
+ }
+
+ @Test
+ public void testSearchWithAuthorizationHeader () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Wasser").queryParam("ql", "poliqarp")
+ .request()
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("test", "pwd"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertTrue(node.at("/collection").isMissingNode());
+ }
+
+ @Test
+ public void testSearchPublicMetadata () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("access-rewrite-disabled", "true").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertTrue(node.at("/matches/0/snippet").isMissingNode());
+ }
+
+ @Test
+ public void testSearchPublicMetadataWithCustomFields ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Sonne").queryParam("ql", "poliqarp")
+ .queryParam("fields", "author,title")
+ .queryParam("access-rewrite-disabled", "true").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertTrue(node.at("/matches/0/snippet").isMissingNode());
+ assertEquals(node.at("/matches/0/author").asText(),
+ "Goethe, Johann Wolfgang von");
+ assertEquals(node.at("/matches/0/title").asText(),
+ "Italienische Reise");
+ // assertEquals(3, node.at("/matches/0").size());
+ }
+
+ @Test
+ public void testSearchPublicMetadataWithNonPublicField ()
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "Sonne").queryParam("ql", "poliqarp")
+ .queryParam("fields", "author,title,snippet")
+ .queryParam("access-rewrite-disabled", "true").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.NON_PUBLIC_FIELD_IGNORED,
+ node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/1").asText(),
+ "The requested non public fields are ignored");
+ assertEquals(node.at("/warnings/0/2").asText(), "snippet");
+ }
+
+ @Test
+ public void testSearchWithInvalidPage () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=die]").queryParam("ql", "poliqarp")
+ .queryParam("page", "0").request().get();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ARGUMENT,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(), "page must start from 1");
+ }
+
+ @Test
+ public void testCloseIndex () throws IOException, KustvaktException {
+ searchKrill.getStatistics(null);
+ assertEquals(true, searchKrill.getIndex().isReaderOpen());
+ Form form = new Form();
+ form.param("token", "secret");
+ Response response = target().path(API_VERSION).path("index")
+ .path("close").request().post(Entity.form(form));
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertEquals(false, searchKrill.getIndex().isReaderOpen());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/lite/LiteSearchPipeTest.java b/src/test/java/de/ids_mannheim/korap/web/lite/LiteSearchPipeTest.java
new file mode 100644
index 0000000..704ae5f
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/lite/LiteSearchPipeTest.java
@@ -0,0 +1,310 @@
+package de.ids_mannheim.korap.web.lite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockserver.integration.ClientAndServer.startClientAndServer;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.Header;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.config.LiteJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class LiteSearchPipeTest extends LiteJerseyTest {
+
+ private ClientAndServer mockServer;
+
+ private MockServerClient mockClient;
+
+ private int port = 6070;
+
+ private String pipeJson, pipeWithParamJson;
+
+ private String glemmUri = "http://localhost:" + port + "/glemm";
+
+ public LiteSearchPipeTest () throws IOException {
+ pipeJson = IOUtils.toString(
+ ClassLoader.getSystemResourceAsStream(
+ "pipe-output/test-pipes.jsonld"),
+ StandardCharsets.UTF_8);
+ pipeWithParamJson = IOUtils.toString(
+ ClassLoader.getSystemResourceAsStream(
+ "pipe-output/with-param.jsonld"),
+ StandardCharsets.UTF_8);
+ }
+
+ @BeforeEach
+ public void startMockServer () {
+ mockServer = startClientAndServer(port);
+ mockClient = new MockServerClient("localhost", mockServer.getPort());
+ }
+
+ @AfterEach
+ public void stopMockServer () {
+ mockServer.stop();
+ }
+
+ @Test
+ public void testMockServer () throws IOException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/test")
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody("{test}").withStatusCode(200));
+ URL url = new URL("http://localhost:" + port + "/test");
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type",
+ "application/json; charset=UTF-8");
+ connection.setRequestProperty("Accept", "application/json");
+ connection.setDoOutput(true);
+ String json = "{\"name\" : \"dory\"}";
+ try (OutputStream os = connection.getOutputStream()) {
+ byte[] input = json.getBytes("utf-8");
+ os.write(input, 0, input.length);
+ }
+ assertEquals(200, connection.getResponseCode());
+ BufferedReader br = new BufferedReader(
+ new InputStreamReader(connection.getInputStream(), "utf-8"));
+ assertEquals(br.readLine(), "{test}");
+ }
+
+ @Test
+ public void testSearchWithPipes ()
+ throws IOException, KustvaktException, URISyntaxException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/glemm")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody(pipeJson).withStatusCode(200));
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", glemmUri).request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/query/wrap/key").size());
+ node = node.at("/query/wrap/rewrites");
+ assertEquals(2, node.size());
+ assertEquals(node.at("/0/src").asText(), "Glemm");
+ assertEquals(node.at("/0/operation").asText(), "operation:override");
+ assertEquals(node.at("/0/scope").asText(), "key");
+ assertEquals(node.at("/1/src").asText(), "Kustvakt");
+ assertEquals(node.at("/1/operation").asText(), "operation:injection");
+ assertEquals(node.at("/1/scope").asText(), "foundry");
+ }
+
+ @Test
+ public void testSearchWithUrlEncodedPipes ()
+ throws IOException, KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/glemm")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody(pipeJson).withStatusCode(200));
+ glemmUri = URLEncoder.encode(glemmUri, "utf-8");
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", glemmUri).request().get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/query/wrap/key").size());
+ }
+
+ @Test
+ public void testSearchWithMultiplePipes () throws KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/glemm")
+ .withQueryStringParameter("param").withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody(pipeWithParamJson).withStatusCode(200));
+ String glemmUri2 = glemmUri + "?param=blah";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", glemmUri + "," + glemmUri2).request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(3, node.at("/query/wrap/key").size());
+ }
+
+ @Test
+ public void testSearchWithUnknownURL ()
+ throws IOException, KustvaktException {
+ String url = target().getUri().toString() + API_VERSION
+ + "/test/tralala";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", url).request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/3").asText(), "404 Not Found");
+ }
+
+ @Test
+ public void testSearchWithUnknownHost () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", "http://glemm").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/3").asText(), "glemm");
+ }
+
+ @Test
+ public void testSearchUnsupportedMediaType () throws KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/non-json-pipe"))
+ .respond(response().withStatusCode(415));
+ String pipeUri = "http://localhost:" + port + "/non-json-pipe";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", pipeUri).request().get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/3").asText(),
+ "415 Unsupported Media Type");
+ }
+
+ @Test
+ public void testSearchWithMultiplePipeWarnings () throws KustvaktException {
+ String url = target().getUri().toString() + API_VERSION
+ + "/test/tralala";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", url + "," + "http://glemm").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/warnings").size());
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/0/0").asInt());
+ assertEquals(url, node.at("/warnings/0/2").asText());
+ assertEquals(node.at("/warnings/0/3").asText(), "404 Not Found");
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/1/0").asInt());
+ assertEquals(node.at("/warnings/1/2").asText(), "http://glemm");
+ assertEquals(node.at("/warnings/1/3").asText(), "glemm");
+ }
+
+ @Test
+ public void testSearchWithInvalidJsonResponse () throws KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/invalid-response")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response().withBody("{blah:}").withStatusCode(200)
+ .withHeaders(new Header("Content-Type",
+ "application/json; charset=utf-8")));
+ String pipeUri = "http://localhost:" + port + "/invalid-response";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", pipeUri).request().get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.DESERIALIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testSearchWithPlainTextResponse () throws KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/plain-text")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response().withBody("blah").withStatusCode(200));
+ String pipeUri = "http://localhost:" + port + "/plain-text";
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", pipeUri).request().get();
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.DESERIALIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ }
+
+ @Test
+ public void testSearchWithMultipleAndUnknownPipes ()
+ throws KustvaktException {
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/glemm")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody(pipeJson).withStatusCode(200));
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", "http://unknown" + "," + glemmUri)
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/query/wrap/key").size());
+ assertTrue(node.at("/warnings").isMissingNode());
+ response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("pipes", glemmUri + ",http://unknown").request()
+ .get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ entity = response.readEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.PIPE_FAILED, node.at("/warnings/0/0").asInt());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/lite/LiteSearchTokenSnippetTest.java b/src/test/java/de/ids_mannheim/korap/web/lite/LiteSearchTokenSnippetTest.java
new file mode 100644
index 0000000..fb78508
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/lite/LiteSearchTokenSnippetTest.java
@@ -0,0 +1,69 @@
+package de.ids_mannheim.korap.web.lite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.config.LiteJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class LiteSearchTokenSnippetTest extends LiteJerseyTest {
+
+ @Test
+ public void testSearchWithTokens () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("show-tokens", "true")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertTrue(node.at("/matches/0/hasSnippet").asBoolean());
+ assertTrue(node.at("/matches/0/hasTokens").asBoolean());
+ assertTrue(node.at("/matches/0/tokens/left").size() > 0);
+ assertTrue(node.at("/matches/0/tokens/right").size() > 0);
+ assertEquals(1, node.at("/matches/0/tokens/match").size());
+ }
+
+ @Test
+ public void testSearchWithoutTokens () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("show-tokens", "false")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertTrue(node.at("/matches/0/hasSnippet").asBoolean());
+ assertFalse(node.at("/matches/0/hasTokens").asBoolean());
+ assertTrue(node.at("/matches/0/tokens").isMissingNode());
+ }
+
+ @Test
+ public void testSearchPublicMetadataWithTokens () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+ .queryParam("access-rewrite-disabled", "true")
+ .queryParam("show-tokens", "true")
+ .queryParam("context", "sentence").queryParam("count", "13")
+ .request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertFalse(node.at("/matches/0/hasSnippet").asBoolean());
+ assertFalse(node.at("/matches/0/hasTokens").asBoolean());
+ assertTrue(node.at("/matches/0/snippet").isMissingNode());
+ assertTrue(node.at("/matches/0/tokens").isMissingNode());
+ assertEquals(StatusCodes.NOT_ALLOWED, node.at("/warnings/0/0").asInt());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/lite/LiteStatisticControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/lite/LiteStatisticControllerTest.java
new file mode 100644
index 0000000..4314ef0
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/lite/LiteStatisticControllerTest.java
@@ -0,0 +1,163 @@
+package de.ids_mannheim.korap.web.lite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.config.LiteJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class LiteStatisticControllerTest extends LiteJerseyTest {
+
+ @Test
+ public void testStatisticsWithCq () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("cq", "textType=Abhandlung & corpusSigle=GOE")
+ .request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ assertEquals(response.getHeaders().getFirst("X-Index-Revision"),
+ "Wes8Bd4h1OypPqbWF5njeQ==");
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertEquals(2, node.at("/documents").asInt());
+ assertEquals(138180, node.at("/tokens").asInt());
+ assertEquals(5687, node.at("/sentences").asInt());
+ assertEquals(258, node.at("/paragraphs").asInt());
+ assertTrue(node.at("/warnings").isMissingNode());
+ }
+
+ @Test
+ public void testStatisticsWithCqAndCorpusQuery () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("cq", "textType=Abhandlung & corpusSigle=GOE")
+ .queryParam("corpusQuery",
+ "textType=Autobiographie & corpusSigle=GOE")
+ .request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertEquals(2, node.at("/documents").asInt());
+ assertEquals(138180, node.at("/tokens").asInt());
+ assertEquals(5687, node.at("/sentences").asInt());
+ assertEquals(258, node.at("/paragraphs").asInt());
+ assertTrue(node.at("/warnings").isMissingNode());
+ }
+
+ @Test
+ public void testStatisticsWithCorpusQuery () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("corpusQuery",
+ "textType=Autobiographie & corpusSigle=GOE")
+ .request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertEquals(9, node.at("/documents").asInt());
+ assertEquals(527662, node.at("/tokens").asInt());
+ assertEquals(19387, node.at("/sentences").asInt());
+ assertEquals(514, node.at("/paragraphs").asInt());
+ assertEquals(StatusCodes.DEPRECATED, node.at("/warnings/0/0").asInt());
+ assertEquals(node.at("/warnings/0/1").asText(),
+ "Parameter corpusQuery is deprecated in favor of cq.");
+ }
+
+ @Test
+ public void testEmptyStatistics () throws KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .queryParam("corpusQuery", "").request().method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String query = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(query);
+ assertEquals(11, node.at("/documents").asInt());
+ assertEquals(665842, node.at("/tokens").asInt());
+ assertEquals(25074, node.at("/sentences").asInt());
+ assertEquals(772, node.at("/paragraphs").asInt());
+ response = target().path(API_VERSION).path("statistics").request()
+ .method("GET");
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ query = response.readEntity(String.class);
+ node = JsonUtils.readTree(query);
+ assertEquals(11, node.at("/documents").asInt());
+ assertEquals(665842, node.at("/tokens").asInt());
+ assertEquals(25074, node.at("/sentences").asInt());
+ assertEquals(772, node.at("/paragraphs").asInt());
+ }
+
+ @Test
+ public void testGetStatisticsWithKoralQuery ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .request()
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .post(Entity.json("{ \"collection\" : {\"@type\": "
+ + "\"koral:doc\", \"key\": \"availability\", \"match\": "
+ + "\"match:eq\", \"type\": \"type:regex\", \"value\": "
+ + "\"CC-BY.*\"} }"));
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ assertEquals(response.getHeaders().getFirst("X-Index-Revision"),
+ "Wes8Bd4h1OypPqbWF5njeQ==");
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(2, node.at("/documents").asInt());
+ assertEquals(72770, node.at("/tokens").asInt());
+ assertEquals(2985, node.at("/sentences").asInt());
+ assertEquals(128, node.at("/paragraphs").asInt());
+ }
+
+ @Test
+ public void testGetStatisticsWithEmptyCollection ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .request()
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .post(Entity.json("{}"));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(node.at("/errors/0/0").asInt(),
+ de.ids_mannheim.korap.util.StatusCodes.MISSING_COLLECTION);
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Collection is not found");
+ }
+
+ @Test
+ public void testGetStatisticsWithIncorrectJson ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .request()
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .post(Entity.json("{ \"collection\" : }"));
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(StatusCodes.DESERIALIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(node.at("/errors/0/1").asText(),
+ "Failed deserializing json object: { \"collection\" : }");
+ }
+
+ @Test
+ public void testGetStatisticsWithoutKoralQuery ()
+ throws IOException, KustvaktException {
+ Response response = target().path(API_VERSION).path("statistics")
+ .request().post(Entity.json(""));
+ String ent = response.readEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(11, node.at("/documents").asInt());
+ assertEquals(665842, node.at("/tokens").asInt());
+ assertEquals(25074, node.at("/sentences").asInt());
+ assertEquals(772, node.at("/paragraphs").asInt());
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/lite/SearchNetworkEndpointTest.java b/src/test/java/de/ids_mannheim/korap/web/lite/SearchNetworkEndpointTest.java
new file mode 100644
index 0000000..81c2430
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/lite/SearchNetworkEndpointTest.java
@@ -0,0 +1,112 @@
+package de.ids_mannheim.korap.web.lite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockserver.integration.ClientAndServer.startClientAndServer;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.Header;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.LiteJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class SearchNetworkEndpointTest extends LiteJerseyTest {
+
+ @Autowired
+ private KustvaktConfiguration config;
+
+ private ClientAndServer mockServer;
+
+ private MockServerClient mockClient;
+
+ private int port = 6080;
+
+ private String searchResult;
+
+ private String endpointURL = "http://localhost:" + port + "/searchEndpoint";
+
+ public SearchNetworkEndpointTest () throws IOException {
+ searchResult = IOUtils.toString(
+ ClassLoader.getSystemResourceAsStream(
+ "network-output/search-result.jsonld"),
+ StandardCharsets.UTF_8);
+ }
+
+ @BeforeEach
+ public void startMockServer () {
+ mockServer = startClientAndServer(port);
+ mockClient = new MockServerClient("localhost", mockServer.getPort());
+ }
+
+ @AfterEach
+ public void stopMockServer () {
+ mockServer.stop();
+ }
+
+ @Test
+ public void testSearchNetwork ()
+ throws IOException, KustvaktException, URISyntaxException {
+ config.setNetworkEndpointURL(endpointURL);
+ mockClient.reset()
+ .when(request().withMethod("POST").withPath("/searchEndpoint")
+ .withHeaders(
+ new Header("Content-Type",
+ "application/json; charset=utf-8"),
+ new Header("Accept", "application/json")))
+ .respond(response()
+ .withHeader(new Header("Content-Type",
+ "application/json; charset=utf-8"))
+ .withBody(searchResult).withStatusCode(200));
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("engine", "network").request().get();
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(2, node.at("/matches").size());
+ }
+
+ @Test
+ public void testSearchWithUnknownURL ()
+ throws IOException, KustvaktException {
+ config.setNetworkEndpointURL("http://localhost:1040/search");
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("engine", "network").request().get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.SEARCH_NETWORK_ENDPOINT_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+
+ @Test
+ public void testSearchWithUnknownHost () throws KustvaktException {
+ config.setNetworkEndpointURL("http://search.com");
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("engine", "network").request().get();
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.SEARCH_NETWORK_ENDPOINT_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+}
diff --git a/src/test/resources/json/oauth2_public_client.json b/src/test/resources/json/oauth2_public_client.json
new file mode 100644
index 0000000..14ab89a
--- /dev/null
+++ b/src/test/resources/json/oauth2_public_client.json
@@ -0,0 +1,6 @@
+{
+ "name":"my client",
+ "type": "PUBLIC",
+ "redirect_uri": "https://my.client.com",
+ "description":"my public client"
+}
\ No newline at end of file
diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties
new file mode 100644
index 0000000..f4cf442
--- /dev/null
+++ b/src/test/resources/junit-platform.properties
@@ -0,0 +1,4 @@
+junit.jupiter.testclass.order.default = org.junit.jupiter.api.ClassOrderer$OrderAnnotation
+junit.jupiter.execution.parallel.enabled = true
+
+
diff --git a/src/test/resources/keystore.p12 b/src/test/resources/keystore.p12
new file mode 100644
index 0000000..a1d7980
--- /dev/null
+++ b/src/test/resources/keystore.p12
Binary files differ
diff --git a/src/test/resources/kustvakt-icc.conf b/src/test/resources/kustvakt-icc.conf
new file mode 100644
index 0000000..84a38a8
--- /dev/null
+++ b/src/test/resources/kustvakt-icc.conf
@@ -0,0 +1,105 @@
+# index dir
+krill.indexDir = wiki-index
+
+krill.index.commit.count = 134217000
+krill.index.commit.log = log/krill.commit.log
+krill.index.commit.auto = 500
+krill.index.relations.max = 100
+# Directory path of virtual corpora to cache
+#krill.namedVC = vc
+krill.test = true
+
+# LDAP
+ldap.config = src/test/resources/test-ldap.conf
+
+# Kustvakt
+# multiple versions separated by space
+current.api.version = v1.0
+supported.api.version = v0.1 v1.0
+
+# server
+server.port=8089
+server.host=localhost
+
+# mail settings
+mail.enabled = false
+mail.receiver = test@localhost
+mail.sender = noreply@ids-mannheim.de
+mail.address.retrieval = test
+
+# mail.templates
+template.group.invitation = notification.vm
+
+# default foundries for specific layers
+default.foundry.partOfSpeech = tt
+default.foundry.lemma = tt
+default.foundry.orthography = opennlp
+default.foundry.dependency = malt
+default.foundry.constituent = corenlp
+default.foundry.morphology = marmot
+default.foundry.surface = base
+
+# delete configuration (default hard)
+# delete.auto.group = hard
+delete.group = soft
+delete.group.member = soft
+
+
+# availability regex only support |
+# It should be removed/commented when the data doesn't contain availability field.
+#
+# availability.regex.free = CC-BY.*
+# availability.regex.public = ACA.*|QAO-NC
+# availability.regex.all = QAO.*
+
+
+# Define resource filters for search and match info web-services
+#
+# AuthenticationFilter activates authentication using OAuth2 tokens
+# DemoUserFilter allows access to the services without login
+#
+# Default values: AuthenticationFilter,DemoUserFilter
+#
+search.resource.filters=AuthenticationFilter
+
+
+# options referring to the security module!
+
+# OAuth
+# (see de.ids_mannheim.korap.constant.AuthenticationMethod for possible
+# oauth.password.authentication values)
+oauth2.password.authentication = TEST
+oauth2.native.client.host = korap.ids-mannheim.de
+oauth2.max.attempts = 2
+# expiry in seconds (S), minutes (M), hours (H), days (D)
+oauth2.access.token.expiry = 3M
+oauth2.refresh.token.expiry = 90D
+oauth2.authorization.code.expiry = 10M
+# -- scopes separated by space
+oauth2.default.scopes = search match_info
+oauth2.client.credentials.scopes = client_info
+
+oauth2.initial.super.client=true
+
+# see SecureRandom Number Generation Algorithms
+# optional
+security.secure.random.algorithm=SHA1PRNG
+
+# see MessageDigest Algorithms
+# default MD5
+security.md.algoritm = SHA-256
+
+# secure hash support: BCRYPT
+security.secure.hash.algorithm=BCRYPT
+security.encryption.loadFactor = 10
+
+
+# DEPRECATED
+# JWT
+security.jwt.issuer=https://korap.ids-mannheim.de
+security.sharedSecret=this-is-shared-secret-code-for-JWT-Signing.It-must-contains-minimum-256-bits
+
+# token expiration time
+security.longTokenTTL = 1D
+security.tokenTTL = 2S
+security.shortTokenTTL = 1S
diff --git a/src/test/resources/kustvakt-test.conf b/src/test/resources/kustvakt-test.conf
new file mode 100644
index 0000000..facc03a
--- /dev/null
+++ b/src/test/resources/kustvakt-test.conf
@@ -0,0 +1,121 @@
+# Krill settings
+#
+krill.indexDir = sample-index
+
+krill.index.commit.count = 134217000
+krill.index.commit.log = log/krill.commit.log
+krill.index.commit.auto = 500
+krill.index.relations.max = 100
+# Directory path of virtual corpora to cache
+krill.namedVC = vc
+krill.test = true
+
+# LDAP configuration file
+#
+ldap.config = src/test/resources/test-ldap.conf
+
+# Kustvakt versions
+#
+# multiple versions separated by space
+current.api.version = v1.0
+supported.api.version = v0.1 v1.0
+
+# Server
+#
+server.port=8089
+server.host=localhost
+
+# Mail settings
+#
+mail.enabled = false
+mail.receiver = test@localhost
+mail.sender = noreply@ids-mannheim.de
+mail.address.retrieval = test
+
+# Mail.templates
+#
+template.group.invitation = notification.vm
+
+# Default foundries for specific layers (optional)
+#
+default.foundry.partOfSpeech = tt
+default.foundry.lemma = tt
+default.foundry.orthography = opennlp
+default.foundry.dependency = malt
+default.foundry.constituent = corenlp
+default.foundry.morphology = marmot
+default.foundry.surface = base
+
+# Delete configuration (default hard)
+#
+# delete.auto.group = hard
+delete.group = soft
+delete.group.member = soft
+
+# Virtual corpus and queries
+max.user.persistent.queries = 5
+
+# Availability regex only support |
+# It should be removed/commented when the data doesn't contain availability field.
+#
+availability.regex.free = CC-BY.*
+availability.regex.public = ACA.*|QAO-NC
+availability.regex.all = QAO.*
+
+
+# Define resource filters for search and match info API
+# AuthenticationFilter activates authentication using OAuth2 tokens
+# DemoUserFilter allows access to API without login
+#
+# Default values: AuthenticationFilter,DemoUserFilter
+#
+search.resource.filters=AuthenticationFilter,DemoUserFilter
+
+
+# options referring to the security module!
+
+# OAuth
+# (see de.ids_mannheim.korap.constant.AuthenticationMethod for possible
+# oauth.password.authentication values)
+#
+oauth2.password.authentication = TEST
+oauth2.native.client.host = korap.ids-mannheim.de
+oauth2.max.attempts = 2
+# expiry in seconds (S), minutes (M), hours (H), days (D)
+oauth2.access.token.expiry = 3M
+oauth2.refresh.token.expiry = 90D
+oauth2.authorization.code.expiry = 10M
+# -- scopes separated by space
+oauth2.default.scopes = search match_info
+oauth2.client.credentials.scopes = client_info
+
+oauth2.initial.super.client=true
+
+
+# see SecureRandom Number Generation Algorithms
+# optional
+security.secure.random.algorithm=SHA1PRNG
+
+# see MessageDigest Algorithms
+# default MD5
+security.md.algoritm = SHA-256
+
+# secure hash support: BCRYPT
+security.secure.hash.algorithm=BCRYPT
+security.encryption.loadFactor = 10
+
+# DEPRECATED
+# JWT
+security.jwt.issuer=https://korap.ids-mannheim.de
+security.sharedSecret=this-is-shared-secret-code-for-JWT-Signing.It-must-contains-minimum-256-bits
+
+# token expiration time
+security.longTokenTTL = 1D
+security.tokenTTL = 2S
+security.shortTokenTTL = 1S
+
+# Session authentication
+security.idleTimeoutDuration = 25M
+security.multipleLogIn = true
+security.loginAttemptNum = 3
+security.authAttemptTTL = 45M
diff --git a/src/test/resources/log4j2-test.properties b/src/test/resources/log4j2-test.properties
new file mode 100644
index 0000000..50d78e7
--- /dev/null
+++ b/src/test/resources/log4j2-test.properties
@@ -0,0 +1,56 @@
+appenders = console,ldapFile
+appender.console.type = Console
+appender.console.name = STDOUT
+appender.console.layout.type = PatternLayout
+appender.console.layout.pattern = %d{yyyy-MM-dd, HH:mm:ss} %C{6} - %M%n %-5p: %m%n
+
+#appender.file.type = File
+#appender.file.name = ERRORLOG
+#appender.file.fileName=./logs/errors.log
+#appender.file.layout.type=PatternLayout
+#appender.file.layout.pattern= %d{yyyy-MM-dd, HH:mm:ss} %C{6} - %M%n %-5p: %m%n
+
+appender.ldapFile.type = File
+appender.ldapFile.name = LDAP_LOG
+appender.ldapFile.fileName=./logs/ldap.log
+appender.ldapFile.layout.type=PatternLayout
+appender.ldapFile.layout.pattern= %d{yyyy-MM-dd, HH:mm:ss} %C{6} - %M%n %-5p: %m%n
+
+rootLogger.level = error
+rootLogger.appenderRefs = console
+rootLogger.appenderRef.stdout.ref = STDOUT
+
+#loggers=console
+#logger.console.name=com.sun.jersey.test.framework.spi.container
+#logger.console.level = info
+#logger.console.appenderRefs = stdout
+#logger.console.appenderRef.file.ref = STDOUT
+#logger.console.additivity=false
+
+#loggers=file
+#logger.file.name=com.sun.jersey.test.framework.spi.container
+#logger.file.level = info
+#logger.file.appenderRefs = file
+#logger.file.appenderRef.file.ref = ERRORLOG
+#logger.file.additivity=false
+
+loggers=console
+logger.console.name=org.hibernate
+logger.console.level = fatal
+logger.console.appenderRefs = stdout
+logger.console.appenderRef.file.ref = STDOUT
+logger.console.additivity=false
+
+loggers=console
+logger.console.name=de.ids_mannheim.korap.web.controller.AuthenticationController
+logger.console.level = warn
+logger.console.appenderRefs = stdout
+logger.console.appenderRef.file.ref = STDOUT
+logger.console.additivity=false
+
+loggers=file
+logger.file.name=de.ids_mannheim.korap.authentication.LdapAuth3
+logger.file.level = info
+logger.file.appenderRefs = file
+logger.file.appenderRef.file.ref = LDAP_LOG
+logger.file.additivity=false
\ No newline at end of file
diff --git a/src/test/resources/network-output/search-result.jsonld b/src/test/resources/network-output/search-result.jsonld
new file mode 100644
index 0000000..da55e09
--- /dev/null
+++ b/src/test/resources/network-output/search-result.jsonld
@@ -0,0 +1,130 @@
+{
+ "@context": "http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld",
+ "meta": {
+ "count": 2,
+ "startIndex": 0,
+ "timeout": 10000,
+ "context": {
+ "left": [
+ "token",
+ 6
+ ],
+ "right": [
+ "token",
+ 6
+ ]
+ },
+ "fields": [
+ "ID",
+ "UID",
+ "textSigle",
+ "corpusID",
+ "author",
+ "title",
+ "subTitle",
+ "textClass",
+ "pubPlace",
+ "pubDate",
+ "availability",
+ "layerInfos",
+ "docSigle",
+ "corpusSigle"
+ ],
+ "version": "0.60.4",
+ "benchmark": "1.503497374 s",
+ "totalResults": 67249248,
+ "serialQuery": "tokens:s:der",
+ "itemsPerPage": 2
+ },
+ "query": {
+ "@type": "koral:token",
+ "wrap": {
+ "@type": "koral:term",
+ "match": "match:eq",
+ "layer": "orth",
+ "key": "der",
+ "foundry": "opennlp",
+ "rewrites": [
+ {
+ "@type": "koral:rewrite",
+ "src": "Kustvakt",
+ "operation": "operation:injection",
+ "scope": "foundry"
+ }
+ ]
+ }
+ },
+ "collection": {
+ "@type": "koral:doc",
+ "match": "match:eq",
+ "type": "type:regex",
+ "value": "CC-BY.*",
+ "key": "availability",
+ "rewrites": [
+ {
+ "@type": "koral:rewrite",
+ "src": "Kustvakt",
+ "operation": "operation:insertion",
+ "scope": "availability(FREE)"
+ }
+ ]
+ },
+ "matches": [
+ {
+ "@context": "http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld",
+ "meta": {},
+ "hasSnippet": true,
+ "hasTokens": false,
+ "matchID": "match-WUD17/B96/48580-p40-41",
+ "textClass": "staat-gesellschaft biographien-interviews fiktion vermischtes",
+ "textSigle": "WUD17/B96/48580",
+ "author": "Andy king50, u.a.",
+ "docSigle": "WUD17/B96",
+ "layerInfos": "corenlp/c=spans corenlp/p=tokens corenlp/s=spans dereko/s=spans malt/d=rels marmot/m=tokens marmot/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens",
+ "pubPlace": "URL:http://de.wikipedia.org",
+ "availability": "CC-BY-SA",
+ "title": "Benutzer Diskussion:Brettspieler",
+ "pubDate": "2017-07-01",
+ "corpusSigle": "WUD17",
+ "context": {
+ "left": [
+ "token",
+ 6
+ ],
+ "right": [
+ "token",
+ 6
+ ]
+ },
+ "snippet": "<span class=\"context-left\"><span class=\"more\"><\/span>konstruktiv mitarbeiten kannst, erfährst du auf <\/span><span class=\"match\"><mark>der<\/mark><\/span><span class=\"context-right\"> Seite Starthilfe . Grüße, 19:05, 6. Nov.<span class=\"more\"><\/span><\/span>"
+ },
+ {
+ "@context": "http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld",
+ "meta": {},
+ "hasSnippet": true,
+ "hasTokens": false,
+ "matchID": "match-WUD17/B96/50115-p40-41",
+ "textClass": "staat-gesellschaft biographien-interviews fiktion vermischtes",
+ "textSigle": "WUD17/B96/50115",
+ "author": "Doc.Heintz, u.a.",
+ "docSigle": "WUD17/B96",
+ "layerInfos": "corenlp/c=spans corenlp/p=tokens corenlp/s=spans dereko/s=spans malt/d=rels marmot/m=tokens marmot/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens",
+ "pubPlace": "URL:http://de.wikipedia.org",
+ "availability": "CC-BY-SA",
+ "title": "Benutzer Diskussion:Blkviz",
+ "pubDate": "2017-07-01",
+ "corpusSigle": "WUD17",
+ "context": {
+ "left": [
+ "token",
+ 6
+ ],
+ "right": [
+ "token",
+ 6
+ ]
+ },
+ "snippet": "<span class=\"context-left\"><span class=\"more\"><\/span>konstruktiv mitarbeiten kannst, erfährst du auf <\/span><span class=\"match\"><mark>der<\/mark><\/span><span class=\"context-right\"> Seite Starthilfe . Grüße, 20:11, 7.<span class=\"more\"><\/span><\/span>"
+ }
+ ]
+}
diff --git a/src/test/resources/pipe-output/test-pipes.jsonld b/src/test/resources/pipe-output/test-pipes.jsonld
new file mode 100644
index 0000000..7fde643
--- /dev/null
+++ b/src/test/resources/pipe-output/test-pipes.jsonld
@@ -0,0 +1,25 @@
+{
+ "meta": {
+ "snippets": true,
+ "timeout": 10000
+ },
+ "query": {
+ "@type": "koral:token",
+ "wrap": {
+ "@type": "koral:term",
+ "match": "match:eq",
+ "key": [
+ "der",
+ "das"
+ ],
+ "layer": "orth",
+ "rewrites": [{
+ "@type": "koral:rewrite",
+ "src": "Glemm",
+ "operation": "operation:override",
+ "scope": "key"
+ }]
+ }
+ },
+ "@context": "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld"
+}
diff --git a/src/test/resources/pipe-output/with-param.jsonld b/src/test/resources/pipe-output/with-param.jsonld
new file mode 100644
index 0000000..08c7fda
--- /dev/null
+++ b/src/test/resources/pipe-output/with-param.jsonld
@@ -0,0 +1,29 @@
+{
+ "meta": {
+ "snippets": true,
+ "timeout": 10000
+ },
+ "query": {
+ "@type": "koral:token",
+ "wrap": {
+ "@type": "koral:term",
+ "match": "match:eq",
+ "key": [
+ "der",
+ "das",
+ "die"
+ ],
+ "layer": "orth",
+ "rewrites": [
+ {
+ "@type": "koral:rewrite",
+ "src": "Glemm",
+ "operation": "operation:override",
+ "scope": "key"
+ }
+ ]
+ }
+ },
+ "@context": "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld"
+}
+
diff --git a/src/test/resources/test-config-icc.xml b/src/test/resources/test-config-icc.xml
new file mode 100644
index 0000000..d418f05
--- /dev/null
+++ b/src/test/resources/test-config-icc.xml
@@ -0,0 +1,351 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:aop="http://www.springframework.org/schema/aop"
+ xmlns:tx="http://www.springframework.org/schema/tx"
+ xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:cache="http://www.springframework.org/schema/cache"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/tx
+ http://www.springframework.org/schema/tx/spring-tx.xsd
+ http://www.springframework.org/schema/aop
+ http://www.springframework.org/schema/aop/spring-aop.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util.xsd">
+
+ <context:component-scan
+ base-package="de.ids_mannheim.korap" />
+ <context:annotation-config />
+
+ <bean id="props"
+ class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+ <property name="ignoreResourceNotFound" value="true" />
+ <property name="locations">
+ <array>
+ <value>file:./kustvakt-icc.conf</value>
+ <value>classpath:kustvakt-icc.conf</value>
+ </array>
+ </property>
+ </bean>
+
+ <bean id="placeholders"
+ class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
+ <property name="ignoreResourceNotFound" value="true" />
+ <property name="locations">
+ <array>
+ <value>classpath:test-jdbc.properties</value>
+ <value>file:./jdbc.properties</value>
+ <value>classpath:properties/mail.properties</value>
+ <value>file:./mail.properties</value>
+ <value>classpath:properties/hibernate.properties</value>
+ <value>file:./kustvakt-icc.conf</value>
+ <value>classpath:kustvakt-icc.conf</value>
+ </array>
+ </property>
+ </bean>
+
+ <!-- <bean id='cacheManager' class='org.springframework.cache.ehcache.EhCacheCacheManager'
+ p:cacheManager-ref='ehcache' /> <bean id='ehcache' class='org.springframework.cache.ehcache.EhCacheManagerFactoryBean'
+ p:configLocation='classpath:ehcache.xml' p:shared='true' /> -->
+ <bean id="dataSource"
+ class="org.apache.commons.dbcp2.BasicDataSource" lazy-init="true">
+ <!-- <property name="driverClassName" value="${jdbc.driverClassName}" /> -->
+ <property name="url" value="${jdbc.url}" />
+ <property name="username" value="${jdbc.username}" />
+ <property name="password" value="${jdbc.password}" />
+ <property name="maxTotal" value="4" />
+ <property name="maxIdle" value="1" />
+ <property name="minIdle" value="1" />
+ <property name="maxWaitMillis" value="15000" />
+ <!--<property name="poolPreparedStatements" value="true"/> -->
+ </bean>
+
+ <!-- use SingleConnection only for testing! -->
+ <bean id="sqliteDataSource"
+ class="org.springframework.jdbc.datasource.SingleConnectionDataSource"
+ lazy-init="true">
+ <!-- <property name="driverClassName" value="${jdbc.driverClassName}" /> -->
+ <property name="url" value="${jdbc.url}" />
+ <property name="username" value="${jdbc.username}" />
+ <property name="password" value="${jdbc.password}" />
+ <property name="connectionProperties">
+ <props>
+ <prop key="date_string_format">yyyy-MM-dd HH:mm:ss</prop>
+ </props>
+ </property>
+
+ <!-- Sqlite can only have a single connection -->
+ <property name="suppressClose">
+ <value>true</value>
+ </property>
+ </bean>
+
+ <bean id="c3p0DataSource"
+ class="com.mchange.v2.c3p0.ComboPooledDataSource"
+ destroy-method="close">
+ <property name="driverClass" value="${jdbc.driverClassName}" />
+ <property name="jdbcUrl" value="${jdbc.url}" />
+ <property name="user" value="${jdbc.username}" />
+ <property name="password" value="${jdbc.password}" />
+ <property name="maxPoolSize" value="4" />
+ <property name="minPoolSize" value="1" />
+ <property name="maxStatements" value="1" />
+ <property name="testConnectionOnCheckout" value="true" />
+ </bean>
+
+ <!-- to configure database for sqlite, mysql, etc. migrations -->
+ <bean id="flywayConfig"
+ class="org.flywaydb.core.api.configuration.ClassicConfiguration">
+ <!-- drop existing tables and create new tables -->
+ <property name="validateOnMigrate" value="true" />
+ <property name="cleanOnValidationError" value="true" />
+ <property name="baselineOnMigrate" value="false" />
+ <property name="locations"
+ value="#{'${jdbc.schemaPath}'.split(',')}" />
+ <property name="dataSource" ref="sqliteDataSource" />
+ <!-- <property name="dataSource" ref="dataSource" /> -->
+ <property name="outOfOrder" value="true" />
+ </bean>
+
+ <bean id="flyway" class="org.flywaydb.core.Flyway"
+ init-method="migrate">
+ <constructor-arg ref="flywayConfig" />
+ </bean>
+
+
+ <bean id="entityManagerFactory"
+ class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
+ <!-- <property name="dataSource" ref="dataSource" /> -->
+ <property name="dataSource" ref="sqliteDataSource" />
+ <property name="packagesToScan">
+ <array>
+ <value>de.ids_mannheim.korap.core.entity</value>
+ <value>de.ids_mannheim.korap.entity</value>
+ <value>de.ids_mannheim.korap.oauth2.entity</value>
+ </array>
+ </property>
+ <property name="jpaVendorAdapter">
+ <bean id="jpaVendorAdapter"
+ class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
+ <property name="databasePlatform"
+ value="${hibernate.dialect}" />
+ </bean>
+ </property>
+ <property name="jpaProperties">
+ <props>
+ <prop key="hibernate.dialect">${hibernate.dialect}</prop>
+ <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
+ <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
+ <prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
+ <prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}
+ </prop>
+ <prop key="hibernate.cache.provider_class">${hibernate.cache.provider}</prop>
+ <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory}</prop>
+ <prop key="hibernate.jdbc.time_zone">${hibernate.jdbc.time_zone}</prop>
+ <!-- <prop key="net.sf.ehcache.configurationResourceName">classpath:ehcache.xml</prop> -->
+ </props>
+ </property>
+ </bean>
+
+ <tx:annotation-driven proxy-target-class="true"
+ transaction-manager="transactionManager" />
+ <bean id="transactionManager"
+ class="org.springframework.orm.jpa.JpaTransactionManager">
+ <property name="entityManagerFactory"
+ ref="entityManagerFactory" />
+ </bean>
+
+ <bean id="transactionTemplate"
+ class="org.springframework.transaction.support.TransactionTemplate">
+ <constructor-arg ref="transactionManager" />
+ </bean>
+
+ <!-- Data access objects -->
+ <bean id="adminDao" class="de.ids_mannheim.korap.dao.AdminDaoImpl" />
+ <bean id="resourceDao"
+ class="de.ids_mannheim.korap.dao.ResourceDao" />
+ <bean id="accessScopeDao"
+ class="de.ids_mannheim.korap.oauth2.dao.AccessScopeDao" />
+ <bean id="authorizationDao"
+ class="de.ids_mannheim.korap.oauth2.dao.CachedAuthorizationDaoImpl" />
+
+ <!-- Services -->
+ <bean id="scopeService"
+ class="de.ids_mannheim.korap.oauth2.service.OAuth2ScopeServiceImpl" />
+
+
+ <!-- Controller -->
+
+
+ <!-- props are injected from default-config.xml -->
+ <bean id="kustvakt_config"
+ class="de.ids_mannheim.korap.config.FullConfiguration">
+ <constructor-arg name="properties" ref="props" />
+ </bean>
+
+ <bean id="initializator"
+ class="de.ids_mannheim.korap.init.Initializator"
+ init-method="initTest">
+ </bean>
+
+ <!-- Krill -->
+ <bean id="search_krill"
+ class="de.ids_mannheim.korap.web.SearchKrill">
+ <constructor-arg value="${krill.indexDir}" />
+ </bean>
+
+ <!-- Validator -->
+ <bean id="validator"
+ class="de.ids_mannheim.korap.validator.ApacheValidator" />
+
+ <!-- URLValidator -->
+ <bean id="redirectURIValidator"
+ class="org.apache.commons.validator.routines.UrlValidator">
+ <constructor-arg value="http,https" index="0" />
+ <constructor-arg index="1" type="long"
+ value="#{T(org.apache.commons.validator.routines.UrlValidator).ALLOW_LOCAL_URLS +
+ T(org.apache.commons.validator.routines.UrlValidator).NO_FRAGMENTS}" />
+ </bean>
+ <bean id="urlValidator"
+ class="org.apache.commons.validator.routines.UrlValidator">
+ <constructor-arg value="http,https" />
+ </bean>
+
+ <!-- Rewrite -->
+ <bean id="foundryRewrite"
+ class="de.ids_mannheim.korap.rewrite.FoundryRewrite" />
+ <bean id="collectionRewrite"
+ class="de.ids_mannheim.korap.rewrite.CollectionRewrite" />
+ <bean id="collectionCleanRewrite"
+ class="de.ids_mannheim.korap.rewrite.CollectionCleanRewrite" />
+ <bean id="virtualCorpusRewrite"
+ class="de.ids_mannheim.korap.rewrite.VirtualCorpusRewrite" />
+ <bean id="collectionConstraint"
+ class="de.ids_mannheim.korap.rewrite.CollectionConstraint" />
+ <bean id="queryReferenceRewrite"
+ class="de.ids_mannheim.korap.rewrite.QueryReferenceRewrite" />
+
+ <util:list id="rewriteTasks"
+ value-type="de.ids_mannheim.korap.rewrite.RewriteTask">
+ <!-- <ref bean="collectionConstraint" /> <ref bean="collectionCleanRewrite"
+ /> -->
+ <ref bean="foundryRewrite" />
+ <!-- <ref bean="collectionRewrite" /> -->
+ <ref bean="virtualCorpusRewrite" />
+ <ref bean="queryReferenceRewrite" />
+ </util:list>
+
+ <bean id="rewriteHandler"
+ class="de.ids_mannheim.korap.rewrite.RewriteHandler">
+ <constructor-arg ref="rewriteTasks" />
+ </bean>
+
+ <bean id="kustvaktResponseHandler"
+ class="de.ids_mannheim.korap.web.KustvaktResponseHandler">
+ </bean>
+
+ <!-- OAuth -->
+ <bean id="oauth2ResponseHandler"
+ class="de.ids_mannheim.korap.web.OAuth2ResponseHandler">
+ </bean>
+
+ <bean name="kustvakt_encryption"
+ class="de.ids_mannheim.korap.encryption.KustvaktEncryption">
+ <constructor-arg ref="kustvakt_config" />
+ </bean>
+
+ <!-- authentication providers to use -->
+ <bean id="basic_auth"
+ class="de.ids_mannheim.korap.authentication.BasicAuthentication" />
+
+ <bean id="oauth2_auth"
+ class="de.ids_mannheim.korap.authentication.OAuth2Authentication" />
+
+
+ <util:list id="kustvakt_authproviders"
+ value-type="de.ids_mannheim.korap.interfaces.AuthenticationIface">
+ <ref bean="basic_auth" />
+ <ref bean="oauth2_auth" />
+ </util:list>
+
+
+ <!-- specify type for constructor argument -->
+ <bean id="authenticationManager"
+ class="de.ids_mannheim.korap.authentication.KustvaktAuthenticationManager">
+ <constructor-arg
+ type="de.ids_mannheim.korap.interfaces.EncryptionIface"
+ ref="kustvakt_encryption" />
+ <constructor-arg ref="kustvakt_config" />
+ <!-- inject authentication providers to use -->
+ <property name="providers" ref="kustvakt_authproviders" />
+ </bean>
+
+ <!-- todo: if db interfaces not loaded via spring, does transaction even
+ work then? -->
+ <!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/>
+ bean below) -->
+ <tx:advice id="txAdvice" transaction-manager="txManager">
+ <!-- the transactional semantics... -->
+ <tx:attributes>
+ <!-- all methods starting with 'get' are read-only -->
+ <tx:method name="get*" read-only="true"
+ rollback-for="KorAPException" />
+ <!-- other methods use the default transaction settings (see below) -->
+ <tx:method name="*" rollback-for="KorAPException" />
+ </tx:attributes>
+ </tx:advice>
+
+ <!-- ensure that the above transactional advice runs for any execution of
+ an operation defined by the service interface -->
+ <aop:config>
+ <aop:pointcut id="service"
+ expression="execution(* de.ids_mannheim.korap.interfaces.db.*.*(..))" />
+ <aop:advisor advice-ref="txAdvice" pointcut-ref="service" />
+ </aop:config>
+
+ <!-- similarly, don't forget the PlatformTransactionManager -->
+ <bean id="txManager"
+ class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
+ <property name="dataSource" ref="dataSource" />
+ </bean>
+
+ <!-- mail -->
+ <bean id="authenticator"
+ class="de.ids_mannheim.korap.service.MailAuthenticator">
+ <constructor-arg index="0" value="${mail.username}" />
+ <constructor-arg index="1" value="${mail.password}" />
+ </bean>
+ <bean id="smtpSession" class="jakarta.mail.Session"
+ factory-method="getInstance">
+ <constructor-arg index="0">
+ <props>
+ <prop key="mail.smtp.submitter">${mail.username}</prop>
+ <prop key="mail.smtp.auth">${mail.auth}</prop>
+ <prop key="mail.smtp.host">${mail.host}</prop>
+ <prop key="mail.smtp.port">${mail.port}</prop>
+ <prop key="mail.smtp.starttls.enable">${mail.starttls.enable}</prop>
+ <prop key="mail.smtp.connectiontimeout">${mail.connectiontimeout}</prop>
+ </props>
+ </constructor-arg>
+ <constructor-arg index="1" ref="authenticator" />
+ </bean>
+ <bean id="mailSender"
+ class="org.springframework.mail.javamail.JavaMailSenderImpl">
+ <property name="session" ref="smtpSession" />
+ </bean>
+ <bean id="velocityEngine"
+ class="org.apache.velocity.app.VelocityEngine">
+ <constructor-arg index="0">
+ <props>
+ <prop key="resource.loader">class</prop>
+ <prop key="class.resource.loader.class">org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
+ </prop>
+ </props>
+ </constructor-arg>
+ </bean>
+</beans>
diff --git a/src/test/resources/test-config-lite.xml b/src/test/resources/test-config-lite.xml
new file mode 100644
index 0000000..5de7bd6
--- /dev/null
+++ b/src/test/resources/test-config-lite.xml
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:tx="http://www.springframework.org/schema/tx"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/tx
+ http://www.springframework.org/schema/tx/spring-tx.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util.xsd">
+
+ <context:component-scan
+ base-package="
+ de.ids_mannheim.korap.core.service,
+ de.ids_mannheim.korap.core.web,
+ de.ids_mannheim.korap.web.filter,
+ de.ids_mannheim.korap.web.utils,
+ de.ids_mannheim.korap.authentication.http" />
+ <context:annotation-config />
+
+ <bean id="placeholders"
+ class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
+ <property name="ignoreResourceNotFound" value="true" />
+ <property name="locations">
+ <array>
+ <value>classpath:test-jdbc.properties</value>
+ <value>file:./test-jdbc.properties</value>
+ <value>classpath:test-hibernate.properties</value>
+ <value>classpath:kustvakt-lite.conf</value>
+ <value>file:./kustvakt-lite.conf</value>
+ </array>
+ </property>
+ </bean>
+
+ <bean id="properties"
+ class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+ <property name="ignoreResourceNotFound" value="true" />
+ <property name="locations">
+ <array>
+ <value>classpath:kustvakt-lite.conf</value>
+ <value>file:./kustvakt-lite.conf</value>
+ </array>
+ </property>
+ </bean>
+
+ <bean id="config"
+ class="de.ids_mannheim.korap.config.KustvaktConfiguration">
+ <constructor-arg index="0" name="properties"
+ ref="properties" />
+ </bean>
+
+ <!-- Database -->
+
+ <bean id="dataSource"
+ class="org.apache.commons.dbcp2.BasicDataSource" lazy-init="true">
+ <!-- <property name="driverClassName" value="${jdbc.driverClassName}" /> -->
+ <property name="url" value="${jdbc.url}" />
+ <property name="username" value="${jdbc.username}" />
+ <property name="password" value="${jdbc.password}" />
+ <property name="maxTotal" value="4" />
+ <property name="maxIdle" value="1" />
+ <property name="minIdle" value="1" />
+ <property name="maxWaitMillis" value="15000" />
+ <!--<property name="poolPreparedStatements" value="true"/> -->
+ </bean>
+
+ <bean id="sqliteDataSource"
+ class="org.springframework.jdbc.datasource.SingleConnectionDataSource"
+ lazy-init="true">
+ <property name="driverClassName"
+ value="${jdbc.driverClassName}" />
+ <property name="url" value="${jdbc.url}" />
+ <property name="username" value="${jdbc.username}" />
+ <property name="password" value="${jdbc.password}" />
+ <property name="connectionProperties">
+ <props>
+ <prop key="date_string_format">yyyy-MM-dd HH:mm:ss</prop>
+ </props>
+ </property>
+
+ <!-- relevant for single connection datasource and sqlite -->
+ <property name="suppressClose">
+ <value>true</value>
+ </property>
+ </bean>
+
+ <bean id="flywayConfig"
+ class="org.flywaydb.core.api.configuration.ClassicConfiguration">
+ <property name="baselineOnMigrate" value="true" />
+ <!-- <property name="validateOnMigrate" value="false" /> -->
+ <!-- <property name="cleanOnValidationError" value="true" /> -->
+ <property name="locations"
+ value="#{'${jdbc.schemaPath}'.split(',')}" />
+ <property name="dataSource" ref="dataSource" />
+ <property name="outOfOrder" value="true" />
+ </bean>
+
+ <bean id="flyway" class="org.flywaydb.core.Flyway"
+ init-method="migrate">
+ <constructor-arg ref="flywayConfig" />
+ </bean>
+
+ <bean id="entityManagerFactory"
+ class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
+ <property name="dataSource" ref="dataSource" />
+
+ <property name="packagesToScan">
+ <array>
+ <value>de.ids_mannheim.korap.core.entity</value>
+ </array>
+ </property>
+ <property name="jpaVendorAdapter">
+ <bean id="jpaVendorAdapter"
+ class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
+ <property name="databasePlatform"
+ value="${hibernate.dialect}" />
+ </bean>
+ </property>
+ <property name="jpaProperties">
+ <props>
+ <prop key="hibernate.dialect">${hibernate.dialect}</prop>
+ <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
+ <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
+ <prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
+ <prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}
+ </prop>
+ <prop key="hibernate.cache.provider_class">${hibernate.cache.provider}</prop>
+ <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory}</prop>
+ <prop key="hibernate.jdbc.time_zone">${hibernate.jdbc.time_zone}</prop>
+ </props>
+ </property>
+ </bean>
+ <tx:annotation-driven proxy-target-class="true"
+ transaction-manager="transactionManager" />
+
+ <bean id="transactionManager"
+ class="org.springframework.orm.jpa.JpaTransactionManager">
+ <property name="entityManagerFactory"
+ ref="entityManagerFactory" />
+ </bean>
+
+ <bean id="transactionTemplate"
+ class="org.springframework.transaction.support.TransactionTemplate">
+ <constructor-arg ref="transactionManager" />
+ </bean>
+ <bean id="txManager"
+ class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
+ <property name="dataSource" ref="sqliteDataSource" />
+ </bean>
+
+ <!-- Search Engine -->
+ <bean id="search_krill"
+ class="de.ids_mannheim.korap.web.SearchKrill">
+ <constructor-arg value="${krill.indexDir}" />
+ </bean>
+
+
+ <!-- Filters -->
+ <!-- <bean id="APIVersionFilter" class="de.ids_mannheim.korap.web.APIVersionFilter"
+ scope="singleton" /> -->
+ <!-- Authentication -->
+ <bean id="authenticationManager"
+ class="de.ids_mannheim.korap.authentication.DummyAuthenticationManager" />
+
+ <!-- Response handler -->
+ <bean id="kustvaktResponseHandler"
+ class="de.ids_mannheim.korap.web.KustvaktResponseHandler">
+ </bean>
+
+ <!-- Controllers -->
+ <!-- added via component-scan <bean id="annotationController" class="de.ids_mannheim.korap.web.controller.AnnotationController"
+ /> <bean id="searchController" class="de.ids_mannheim.korap.web.controller.SearchController"
+ /> <bean id="statisticController" class="de.ids_mannheim.korap.web.controller.StatisticController"
+ /> -->
+ <!-- Services -->
+ <bean id="scopeService"
+ class="de.ids_mannheim.korap.oauth2.service.DummyOAuth2ScopeServiceImpl" />
+
+ <!-- DAO -->
+ <bean id="adminDao"
+ class="de.ids_mannheim.korap.dao.DummyAdminDaoImpl" />
+ <bean id="annotationDao"
+ class="de.ids_mannheim.korap.dao.AnnotationDao" />
+
+ <!-- DTO Converter -->
+ <bean id="annotationConverter"
+ class="de.ids_mannheim.korap.dto.converter.AnnotationConverter" />
+
+ <!-- Rewrite -->
+ <bean id="layerMapper"
+ class="de.ids_mannheim.korap.rewrite.LayerMapper" />
+ <bean id="foundryInject"
+ class="de.ids_mannheim.korap.rewrite.FoundryInject" />
+
+ <util:list id="rewriteTasks"
+ value-type="de.ids_mannheim.korap.rewrite.RewriteTask">
+ <ref bean="foundryInject" />
+ </util:list>
+
+ <bean id="rewriteHandler"
+ class="de.ids_mannheim.korap.rewrite.RewriteHandler">
+ <constructor-arg ref="rewriteTasks" />
+ </bean>
+</beans>
\ No newline at end of file
diff --git a/src/test/resources/test-config.xml b/src/test/resources/test-config.xml
new file mode 100644
index 0000000..d1dd61c
--- /dev/null
+++ b/src/test/resources/test-config.xml
@@ -0,0 +1,359 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:aop="http://www.springframework.org/schema/aop"
+ xmlns:tx="http://www.springframework.org/schema/tx"
+ xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:cache="http://www.springframework.org/schema/cache"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/tx
+ http://www.springframework.org/schema/tx/spring-tx.xsd
+ http://www.springframework.org/schema/aop
+ http://www.springframework.org/schema/aop/spring-aop.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util.xsd">
+
+ <context:component-scan
+ base-package="de.ids_mannheim.korap" />
+ <context:annotation-config />
+
+ <bean id="props"
+ class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+ <property name="ignoreResourceNotFound" value="true" />
+ <property name="locations">
+ <array>
+ <value>classpath:kustvakt-test.conf</value>
+ <value>file:./kustvakt-test.conf</value>
+ </array>
+ </property>
+ </bean>
+
+ <bean id="placeholders"
+ class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
+ <property name="ignoreResourceNotFound" value="true" />
+ <property name="locations">
+ <array>
+ <value>classpath:test-jdbc.properties</value>
+ <value>file:./test-jdbc.properties</value>
+ <value>classpath:properties/mail.properties</value>
+ <value>file:./mail.properties</value>
+ <value>classpath:test-hibernate.properties</value>
+ <value>file:./kustvakt-test.conf</value>
+ <value>classpath:kustvakt-test.conf</value>
+ </array>
+ </property>
+ </bean>
+
+ <!-- <bean id='cacheManager' class='org.springframework.cache.ehcache.EhCacheCacheManager'
+ p:cacheManager-ref='ehcache' /> <bean id='ehcache' class='org.springframework.cache.ehcache.EhCacheManagerFactoryBean'
+ p:configLocation='classpath:ehcache.xml' p:shared='true' /> -->
+
+ <bean id="dataSource"
+ class="org.apache.commons.dbcp2.BasicDataSource" lazy-init="true">
+ <!-- <property name="driverClassName" value="${jdbc.driverClassName}" /> -->
+ <property name="url" value="${jdbc.url}" />
+ <property name="username" value="${jdbc.username}" />
+ <property name="password" value="${jdbc.password}" />
+ <property name="maxTotal" value="4" />
+ <property name="maxIdle" value="1" />
+ <property name="minIdle" value="1" />
+ <property name="maxWaitMillis" value="15000" />
+ <!--<property name="poolPreparedStatements" value="true"/> -->
+ </bean>
+
+ <!-- use SingleConnection only for testing! -->
+ <bean id="sqliteDataSource"
+ class="org.springframework.jdbc.datasource.SingleConnectionDataSource"
+ lazy-init="true">
+ <!-- <property name="driverClassName" value="${jdbc.driverClassName}" /> -->
+ <property name="url" value="${jdbc.url}" />
+ <property name="username" value="${jdbc.username}" />
+ <property name="password" value="${jdbc.password}" />
+ <property name="connectionProperties">
+ <props>
+ <prop key="date_string_format">yyyy-MM-dd HH:mm:ss</prop>
+ </props>
+ </property>
+
+ <!-- Sqlite can only have a single connection -->
+ <property name="suppressClose">
+ <value>true</value>
+ </property>
+ </bean>
+
+ <bean id="c3p0DataSource"
+ class="com.mchange.v2.c3p0.ComboPooledDataSource"
+ destroy-method="close">
+ <property name="driverClass" value="${jdbc.driverClassName}" />
+ <property name="jdbcUrl" value="${jdbc.url}" />
+ <property name="user" value="${jdbc.username}" />
+ <property name="password" value="${jdbc.password}" />
+ <property name="maxPoolSize" value="4" />
+ <property name="minPoolSize" value="1" />
+ <property name="maxStatements" value="1" />
+ <property name="testConnectionOnCheckout" value="true" />
+ </bean>
+
+ <!-- to configure database for sqlite, mysql, etc. migrations -->
+ <bean id="flywayConfig"
+ class="org.flywaydb.core.api.configuration.ClassicConfiguration">
+ <!-- drop existing tables and create new tables -->
+ <property name="validateOnMigrate" value="true" />
+ <property name="cleanOnValidationError" value="true" />
+ <property name="baselineOnMigrate" value="false" />
+ <property name="locations"
+ value="#{'${jdbc.schemaPath}'.split(',')}" />
+ <!-- <property name="dataSource" ref="sqliteDataSource" /> -->
+ <property name="dataSource" ref="dataSource" />
+ <property name="outOfOrder" value="true" />
+ </bean>
+
+ <bean id="flyway" class="org.flywaydb.core.Flyway"
+ init-method="migrate">
+ <constructor-arg ref="flywayConfig" />
+ </bean>
+
+
+ <bean id="entityManagerFactory"
+ class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
+ <property name="dataSource" ref="dataSource" />
+ <!-- <property name="dataSource" ref="sqliteDataSource" /> -->
+ <property name="packagesToScan">
+ <array>
+ <value>de.ids_mannheim.korap.core.entity</value>
+ <value>de.ids_mannheim.korap.entity</value>
+ <value>de.ids_mannheim.korap.oauth2.entity</value>
+ </array>
+ </property>
+ <property name="jpaVendorAdapter">
+ <bean id="jpaVendorAdapter"
+ class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
+ <property name="databasePlatform"
+ value="${hibernate.dialect}" />
+ </bean>
+ </property>
+ <property name="jpaProperties">
+ <props>
+ <prop key="hibernate.dialect">${hibernate.dialect}</prop>
+ <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
+ <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
+ <prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
+ <prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}
+ </prop>
+ <prop key="hibernate.cache.provider_class">${hibernate.cache.provider}</prop>
+ <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory}</prop>
+ <prop key="hibernate.jdbc.time_zone">${hibernate.jdbc.time_zone}</prop>
+ <!-- <prop key="net.sf.ehcache.configurationResourceName">classpath:ehcache.xml</prop> -->
+ </props>
+ </property>
+ </bean>
+
+ <tx:annotation-driven proxy-target-class="true"
+ transaction-manager="transactionManager" />
+ <bean id="transactionManager"
+ class="org.springframework.orm.jpa.JpaTransactionManager">
+ <property name="entityManagerFactory"
+ ref="entityManagerFactory" />
+ </bean>
+
+ <bean id="transactionTemplate"
+ class="org.springframework.transaction.support.TransactionTemplate">
+ <constructor-arg ref="transactionManager" />
+ </bean>
+
+ <!-- Data access objects -->
+ <bean id="adminDao" class="de.ids_mannheim.korap.dao.AdminDaoImpl" />
+ <bean id="resourceDao"
+ class="de.ids_mannheim.korap.dao.ResourceDao" />
+ <bean id="accessScopeDao"
+ class="de.ids_mannheim.korap.oauth2.dao.AccessScopeDao" />
+ <bean id="authorizationDao"
+ class="de.ids_mannheim.korap.oauth2.dao.CachedAuthorizationDaoImpl" />
+
+ <!-- Services -->
+ <bean id="scopeService"
+ class="de.ids_mannheim.korap.oauth2.service.OAuth2ScopeServiceImpl" />
+
+ <!-- props are injected from default-config.xml -->
+ <bean id="kustvakt_config"
+ class="de.ids_mannheim.korap.config.FullConfiguration">
+ <constructor-arg name="properties" ref="props" />
+ </bean>
+
+ <bean id="initializator"
+ class="de.ids_mannheim.korap.init.Initializator"
+ init-method="initTest">
+ </bean>
+
+ <!-- Krill -->
+ <bean id="search_krill"
+ class="de.ids_mannheim.korap.web.SearchKrill">
+ <constructor-arg value="${krill.indexDir}" />
+ </bean>
+
+ <!-- Validator -->
+ <bean id="validator"
+ class="de.ids_mannheim.korap.validator.ApacheValidator" />
+
+ <!-- URLValidator -->
+ <bean id="redirectURIValidator"
+ class="org.apache.commons.validator.routines.UrlValidator">
+ <constructor-arg value="http,https" index="0" />
+ <constructor-arg index="1" type="long"
+ value="#{T(org.apache.commons.validator.routines.UrlValidator).ALLOW_LOCAL_URLS +
+ T(org.apache.commons.validator.routines.UrlValidator).NO_FRAGMENTS}" />
+ </bean>
+ <bean id="urlValidator"
+ class="org.apache.commons.validator.routines.UrlValidator">
+ <constructor-arg value="http,https" />
+ </bean>
+
+ <!-- Rewrite -->
+ <bean id="foundryRewrite"
+ class="de.ids_mannheim.korap.rewrite.FoundryRewrite" />
+ <bean id="collectionRewrite"
+ class="de.ids_mannheim.korap.rewrite.CollectionRewrite" />
+ <bean id="collectionCleanRewrite"
+ class="de.ids_mannheim.korap.rewrite.CollectionCleanRewrite" />
+ <bean id="virtualCorpusRewrite"
+ class="de.ids_mannheim.korap.rewrite.VirtualCorpusRewrite" />
+ <bean id="collectionConstraint"
+ class="de.ids_mannheim.korap.rewrite.CollectionConstraint" />
+ <bean id="queryReferenceRewrite"
+ class="de.ids_mannheim.korap.rewrite.QueryReferenceRewrite" />
+
+ <util:list id="rewriteTasks"
+ value-type="de.ids_mannheim.korap.rewrite.RewriteTask">
+ <!-- <ref bean="collectionConstraint" /> <ref bean="collectionCleanRewrite"
+ /> -->
+ <ref bean="foundryRewrite" />
+ <ref bean="collectionRewrite" />
+ <ref bean="virtualCorpusRewrite" />
+ <ref bean="queryReferenceRewrite" />
+ </util:list>
+
+ <bean id="rewriteHandler"
+ class="de.ids_mannheim.korap.rewrite.RewriteHandler">
+ <constructor-arg ref="rewriteTasks" />
+ </bean>
+
+ <bean id="kustvaktResponseHandler"
+ class="de.ids_mannheim.korap.web.KustvaktResponseHandler">
+ </bean>
+
+ <!-- OAuth -->
+ <bean id="oauth2ResponseHandler"
+ class="de.ids_mannheim.korap.web.OAuth2ResponseHandler">
+ </bean>
+
+ <bean name="kustvakt_encryption"
+ class="de.ids_mannheim.korap.encryption.KustvaktEncryption">
+ <constructor-arg ref="kustvakt_config" />
+ </bean>
+
+ <!-- authentication providers to use -->
+ <bean id="basic_auth"
+ class="de.ids_mannheim.korap.authentication.BasicAuthentication" />
+
+
+ <bean id="session_auth"
+ class="de.ids_mannheim.korap.authentication.SessionAuthentication">
+ <constructor-arg
+ type="de.ids_mannheim.korap.config.KustvaktConfiguration"
+ ref="kustvakt_config" />
+ <constructor-arg
+ type="de.ids_mannheim.korap.interfaces.EncryptionIface"
+ ref="kustvakt_encryption" />
+ </bean>
+
+ <bean id="oauth2_auth"
+ class="de.ids_mannheim.korap.authentication.OAuth2Authentication" />
+
+
+ <util:list id="kustvakt_authproviders"
+ value-type="de.ids_mannheim.korap.interfaces.AuthenticationIface">
+ <ref bean="basic_auth" />
+ <ref bean="session_auth" />
+ <ref bean="oauth2_auth" />
+ </util:list>
+
+ <!-- specify type for constructor argument -->
+ <bean id="authenticationManager"
+ class="de.ids_mannheim.korap.authentication.KustvaktAuthenticationManager">
+ <constructor-arg
+ type="de.ids_mannheim.korap.interfaces.EncryptionIface"
+ ref="kustvakt_encryption" />
+ <constructor-arg ref="kustvakt_config" />
+ <!-- inject authentication providers to use -->
+ <property name="providers" ref="kustvakt_authproviders" />
+ </bean>
+
+ <!-- todo: if db interfaces not loaded via spring, does transaction even
+ work then? -->
+ <!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/>
+ bean below) -->
+ <tx:advice id="txAdvice" transaction-manager="txManager">
+ <!-- the transactional semantics... -->
+ <tx:attributes>
+ <!-- all methods starting with 'get' are read-only -->
+ <tx:method name="get*" read-only="true"
+ rollback-for="KorAPException" />
+ <!-- other methods use the default transaction settings (see below) -->
+ <tx:method name="*" rollback-for="KorAPException" />
+ </tx:attributes>
+ </tx:advice>
+
+ <!-- ensure that the above transactional advice runs for any execution of
+ an operation defined by the service interface -->
+ <aop:config>
+ <aop:pointcut id="service"
+ expression="execution(* de.ids_mannheim.korap.interfaces.db.*.*(..))" />
+ <aop:advisor advice-ref="txAdvice" pointcut-ref="service" />
+ </aop:config>
+
+ <!-- similarly, don't forget the PlatformTransactionManager -->
+ <bean id="txManager"
+ class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
+ <property name="dataSource" ref="dataSource" />
+ </bean>
+
+ <!-- mail -->
+ <bean id="authenticator"
+ class="de.ids_mannheim.korap.service.MailAuthenticator">
+ <constructor-arg index="0" value="${mail.username}" />
+ <constructor-arg index="1" value="${mail.password}" />
+ </bean>
+ <bean id="smtpSession" class="jakarta.mail.Session"
+ factory-method="getInstance">
+ <constructor-arg index="0">
+ <props>
+ <prop key="mail.smtp.submitter">${mail.username}</prop>
+ <prop key="mail.smtp.auth">${mail.auth}</prop>
+ <prop key="mail.smtp.host">${mail.host}</prop>
+ <prop key="mail.smtp.port">${mail.port}</prop>
+ <prop key="mail.smtp.starttls.enable">${mail.starttls.enable}</prop>
+ <prop key="mail.smtp.connectiontimeout">${mail.connectiontimeout}</prop>
+ </props>
+ </constructor-arg>
+ <constructor-arg index="1" ref="authenticator" />
+ </bean>
+ <bean id="mailSender"
+ class="org.springframework.mail.javamail.JavaMailSenderImpl">
+ <property name="session" ref="smtpSession" />
+ </bean>
+ <bean id="velocityEngine"
+ class="org.apache.velocity.app.VelocityEngine">
+ <constructor-arg index="0">
+ <props>
+ <prop key="resource.loader">class</prop>
+ <prop key="class.resource.loader.class">org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
+ </prop>
+ </props>
+ </constructor-arg>
+ </bean>
+</beans>
diff --git a/src/test/resources/test-embedded-ldap-users.ldif b/src/test/resources/test-embedded-ldap-users.ldif
new file mode 100644
index 0000000..8760df9
--- /dev/null
+++ b/src/test/resources/test-embedded-ldap-users.ldif
@@ -0,0 +1,46 @@
+dn: dc=example,dc=com
+dc: example
+ou: people
+objectClass: dcObject
+objectClass: organizationalUnit
+
+dn: ou=people,dc=example,dc=com
+ou: people
+objectClass: organizationalUnit
+
+dn: uid=user,ou=people,dc=example,dc=com
+cn: user
+uid: user
+mail: user@example.com
+userPassword: {BASE64}cGFzc3dvcmQ=
+
+dn: uid=user1,ou=people,dc=example,dc=com
+cn: user1
+uid: user1
+mail: user1@example.com
+userPassword: {CLEAR}password1
+
+dn: uid=user2,ou=people,dc=example,dc=com
+cn: user2
+uid: user2
+mail: user2@example.com
+userPassword: password2
+
+dn: uid=user3,ou=people,dc=example,dc=com
+cn: user3
+uid: user3
+mail: user3@example.com
+userPassword: {SHA}ERnP037iRzV+A0oI2ETuol9v0g8=
+
+dn: uid=user4,ou=people,dc=example,dc=com
+cn: user4
+uid: user4
+mail: user4@example.com
+userPassword: {SHA256}uXhzpA9zq+3Y1oWnzV5fheSpz7g+rCaIZkCggThQEis=
+
+dn: uid=user5,ou=people,dc=example,dc=com
+cn: user5
+uid: user5
+mail: user5@example.com
+userPassword: {PBKDF2-SHA256}26PFrg++/nI8YOiHum5MyAMp0HdqKMNOcLpY5RuO2bY=
+
diff --git a/src/test/resources/test-embedded-ldap.conf b/src/test/resources/test-embedded-ldap.conf
new file mode 100644
index 0000000..fb9e079
--- /dev/null
+++ b/src/test/resources/test-embedded-ldap.conf
@@ -0,0 +1,10 @@
+# default and sample configuration for an automatically starting
+# embedded LDAP server
+host=localhost
+port=3267
+searchBase=dc=example,dc=com
+sLoginDN=cn=admin,dc=example,dc=com
+pwd=admin
+searchFilter=(uid=${login})
+useEmbeddedServer=true
+ldifFile=src/test/resources/test-embedded-ldap-users.ldif
diff --git a/src/test/resources/test-hibernate.properties b/src/test/resources/test-hibernate.properties
new file mode 100644
index 0000000..e394a88
--- /dev/null
+++ b/src/test/resources/test-hibernate.properties
@@ -0,0 +1,8 @@
+hibernate.dialect=org.hibernate.community.dialect.SQLiteDialect
+hibernate.hbm2ddl.auto=none
+hibernate.show_sql=false
+hibernate.cache.use_query_cache=false
+hibernate.cache.use_second_level_cache=false
+hibernate.cache.provider=org.hibernate.cache.EhCacheProvider
+hibernate.cache.region.factory=org.hibernate.cache.ehcache.EhCacheRegionFactory
+hibernate.jdbc.time_zone=UTC
\ No newline at end of file
diff --git a/src/test/resources/test-invalid-signature.token b/src/test/resources/test-invalid-signature.token
new file mode 100644
index 0000000..665b76d
--- /dev/null
+++ b/src/test/resources/test-invalid-signature.token
@@ -0,0 +1 @@
+eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MTMwOTYwMjA0NjYsInN1YiI6InRlc3RVc2VyIiwiaXNzIjoiaHR0cDpcL1wva29yYXAuaWRzLW1hbm5oZWltLmRlIn0.n4BhCXsFMizEHepNK5AnF32a3kxyvgiesth74ZHimEY
\ No newline at end of file
diff --git a/src/test/resources/test-jdbc.properties b/src/test/resources/test-jdbc.properties
new file mode 100644
index 0000000..1be6a41
--- /dev/null
+++ b/src/test/resources/test-jdbc.properties
@@ -0,0 +1,11 @@
+#-------------------------------------------------------------------------------
+# Sqlite Settings
+
+jdbc.database=sqlite
+jdbc.driverClassName=org.sqlite.JDBC
+#jdbc.url=jdbc:sqlite::memory:
+jdbc.url=jdbc:sqlite:file::memory:?cache=shared
+#jdbc.url=jdbc:sqlite:testDB.sqlite
+jdbc.username=pc
+jdbc.password=pc
+jdbc.schemaPath=classpath:db/sqlite,db/predefined,db/test
\ No newline at end of file
diff --git a/src/test/resources/test-ldap-users.ldif b/src/test/resources/test-ldap-users.ldif
new file mode 100644
index 0000000..0b92701
--- /dev/null
+++ b/src/test/resources/test-ldap-users.ldif
@@ -0,0 +1,75 @@
+dn: dc=example,dc=com
+dc: example
+ou: people
+objectClass: dcObject
+objectClass: organizationalUnit
+
+dn: ou=people,dc=example,dc=com
+ou: people
+objectClass: organizationalUnit
+
+dn: uid=testuser,ou=people,dc=example,dc=com
+cn: Peter Testuser
+sn: Testuser
+givenName: Peter
+mail: testuser@example.com
+userPassword: cGFzc3dvcmQ=
+displayName: Dr. Peter Testuser
+registered: TRUE
+extraProfile: testuser123
+extraPassword: password
+uid: testuser
+idsC2Profile: idsTestUser
+
+dn: uid=test,ou=people,dc=example,dc=com
+cn: Peter Test
+sn: Test
+givenName: Peter
+mail: test@example.com
+userPassword: top*ecret
+displayName: Dr. Peter Test
+registered: TRUE
+userStatus: 1
+extraProfile: test
+extraPassword: top*ecret
+uid: test
+
+dn: uid=not_registered_user,ou=people,dc=example,dc=com
+mail: not_registered_user@example.com
+userPassword: cGFzc3dvcmQ=
+userStatus: 0
+registered: FALSE
+extraProfile: not_registered_user
+extraPassword: topsecret
+uid: not_registered_user
+
+dn: uid=nameOfBlockedUser,ou=people,dc=example,dc=com
+mail: nameOfBlockedUser@example.com
+userPassword: cGFzc3dvcmQ=
+userStatus: 2
+registered: TRUE
+extraPassword: topsecret
+uid: nameOfBlockedUser
+
+dn: uid=testuser2,ou=people,dc=example,dc=com
+cn: Peter Testuser
+sn: Testuser2
+givenName: Peter
+mail: peter@example.org
+userPassword: cGFzc3dvcmQ=
+displayName: Dr. Peter Testuser
+userStatus: 0
+registered: TRUE
+extraProfile: testuser2
+extraPassword: topsecret
+extraNews: TRUE
+title: Herr
+uid: testuser2
+
+dn: uid=uid,ou=people,dc=example,dc=com
+mail: mail@example.org
+userPassword: userPassword
+registered: TRUE
+extraProfile: extraProfile
+extraPassword: extraPassword
+uid: uid
diff --git a/src/test/resources/test-ldap.conf b/src/test/resources/test-ldap.conf
new file mode 100644
index 0000000..614275c
--- /dev/null
+++ b/src/test/resources/test-ldap.conf
@@ -0,0 +1,8 @@
+host=localhost
+port=3268
+searchBase=dc=example,dc=com
+sLoginDN=cn=admin,dc=example,dc=com
+pwd=adminpassword
+searchFilter=(&(|(uid=${login})(mail=${login})(extraProfile=${login}))(|(userPassword=${password})(extraPassword=${password})))
+authFilter=(registered=TRUE)
+userNotBlockedFilter=(|(userStatus=0)(userStatus=1)(!(userStatus=*)))
diff --git a/src/test/resources/test-ldaps-with-truststore.conf b/src/test/resources/test-ldaps-with-truststore.conf
new file mode 100644
index 0000000..22d0899
--- /dev/null
+++ b/src/test/resources/test-ldaps-with-truststore.conf
@@ -0,0 +1,10 @@
+host=localhost
+port=3269
+useSSL=true
+trustStore=src/test/resources/truststore.jks
+searchBase=dc=example,dc=com
+sLoginDN=cn=admin,dc=example,dc=com
+pwd=adminpassword
+searchFilter=(&(|(uid=${login})(mail=${login})(extraProfile=${login}))(|(userPassword=${password})(extraPassword=${password})))
+authFilter=(registered=TRUE)
+userNotBlockedFilter=(|(userStatus=0)(userStatus=1)(!(userStatus=*)))
diff --git a/src/test/resources/test-ldaps.conf b/src/test/resources/test-ldaps.conf
new file mode 100644
index 0000000..dfbed4f
--- /dev/null
+++ b/src/test/resources/test-ldaps.conf
@@ -0,0 +1,10 @@
+host=localhost
+port=3269
+useSSL=true
+trustStore=
+searchBase=dc=example,dc=com
+sLoginDN=cn=admin,dc=example,dc=com
+pwd=adminpassword
+searchFilter=(&(|(uid=${login})(mail=${login})(extraProfile=${login}))(|(userPassword=${password})(extraPassword=${password})))
+authFilter=(registered=TRUE)
+userNotBlockedFilter=(|(userStatus=0)(userStatus=1)(!(userStatus=*)))
diff --git a/src/test/resources/test-resource-config.xml b/src/test/resources/test-resource-config.xml
new file mode 100644
index 0000000..f26182c
--- /dev/null
+++ b/src/test/resources/test-resource-config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:aop="http://www.springframework.org/schema/aop"
+ xmlns:tx="http://www.springframework.org/schema/tx"
+ xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:cache="http://www.springframework.org/schema/cache"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/tx
+ http://www.springframework.org/schema/tx/spring-tx.xsd
+ http://www.springframework.org/schema/aop
+ http://www.springframework.org/schema/aop/spring-aop.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util.xsd">
+
+ <import resource="classpath:test-config.xml" />
+ <bean id="initializator"
+ class="de.ids_mannheim.korap.init.Initializator"
+ init-method="initResourceTest">
+ </bean>
+
+</beans>
\ No newline at end of file
diff --git a/src/test/resources/truststore.jks b/src/test/resources/truststore.jks
new file mode 100644
index 0000000..50804be
--- /dev/null
+++ b/src/test/resources/truststore.jks
Binary files differ
diff --git a/src/test/resources/vc/named-vc1.jsonld b/src/test/resources/vc/named-vc1.jsonld
new file mode 100644
index 0000000..ec25031
--- /dev/null
+++ b/src/test/resources/vc/named-vc1.jsonld
@@ -0,0 +1,10 @@
+{"collection": {
+ "@type": "koral:doc",
+ "key": "textSigle",
+ "match": "match:eq",
+ "type" : "type:string",
+ "value": [
+ "GOE/AGF/00000",
+ "GOE/AGA/01784"
+ ]
+}}
diff --git a/src/test/resources/vc/named-vc2.jsonld b/src/test/resources/vc/named-vc2.jsonld
new file mode 100644
index 0000000..a79d6e5
--- /dev/null
+++ b/src/test/resources/vc/named-vc2.jsonld
@@ -0,0 +1,10 @@
+{"collection": {
+ "@type": "koral:doc",
+ "key": "textSigle",
+ "match": "match:ne",
+ "type" : "type:string",
+ "value": [
+ "GOE/AGI/04846",
+ "GOE/AGA/01784"
+ ]
+}}
diff --git a/src/test/resources/vc/named-vc3.jsonld b/src/test/resources/vc/named-vc3.jsonld
new file mode 100644
index 0000000..a690744
--- /dev/null
+++ b/src/test/resources/vc/named-vc3.jsonld
@@ -0,0 +1,9 @@
+{
+ "collection": {
+ "@type": "koral:doc",
+ "key": "title",
+ "match": "match:eq",
+ "type": "type:string",
+ "value": "Italienische Reise"
+ }
+}