Add a large-context group

allowing users to access larger context by request [AI-assisted, #745]

The group is automatically generated at initialization, if it doesn't
exist. The size of the context may be larger than the usual max token
context for other users. It is configurable with
"max.token.context.size.large" property in the kustvakt.config file.

Change-Id: I83986a858a3646f7061277cdbba1f4327ebdecfe
diff --git a/src/test/java/de/ids_mannheim/korap/dao/LargeContextGroupTest.java b/src/test/java/de/ids_mannheim/korap/dao/LargeContextGroupTest.java
new file mode 100644
index 0000000..e185cbd
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/dao/LargeContextGroupTest.java
@@ -0,0 +1,37 @@
+package de.ids_mannheim.korap.dao;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import de.ids_mannheim.korap.constant.UserGroupStatus;
+import de.ids_mannheim.korap.entity.UserGroup;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+
+/**
+ * Tests that the LargeContextGroup is correctly created during
+ * initialization (see {@link de.ids_mannheim.korap.init.Initializator#initTest()}).
+ *
+ * @author auto-generated
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration("classpath:test-config.xml")
+public class LargeContextGroupTest extends DaoTestBase {
+
+    private static final String LARGE_CONTEXT_GROUP_NAME = "LargeContextGroup";
+    private static final String LARGE_CONTEXT_GROUP_ADMIN = "korap_admin";
+
+    @Test
+    public void testLargeContextGroupExists () throws KustvaktException {
+        UserGroup group = userGroupDao.retrieveGroupByName(
+                LARGE_CONTEXT_GROUP_NAME, false);
+        assertNotNull(group, "LargeContextGroup should exist after initialization");
+        assertEquals(LARGE_CONTEXT_GROUP_NAME, group.getName());
+        assertEquals(LARGE_CONTEXT_GROUP_ADMIN, group.getCreatedBy());
+        assertEquals(UserGroupStatus.ACTIVE, group.getStatus());
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/de/ids_mannheim/korap/rewrite/QueryContextRewriteTest.java b/src/test/java/de/ids_mannheim/korap/rewrite/QueryContextRewriteTest.java
index 5a596c2..f4f47a6 100644
--- a/src/test/java/de/ids_mannheim/korap/rewrite/QueryContextRewriteTest.java
+++ b/src/test/java/de/ids_mannheim/korap/rewrite/QueryContextRewriteTest.java
@@ -7,6 +7,8 @@
 
 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;
@@ -14,7 +16,10 @@
 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.Form;
 import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
 
 public class QueryContextRewriteTest extends SpringJerseyTest {
     
@@ -24,6 +29,10 @@
     @Autowired
     private KustvaktConfiguration config;
 
+    private static final String LARGE_CONTEXT_GROUP = "LargeContextGroup";
+    private static final String LARGE_CONTEXT_GROUP_ADMIN = "korap_admin";
+    private static final String TEST_USER = "largeContextTestUser";
+
     @Test
     public void testCutTokenContext () throws KustvaktException, Exception {
         Response response = target().path(API_VERSION).path("search")
@@ -44,6 +53,62 @@
         assertEquals(config.getMaxTokenContext(), context.at("/left/1").asInt());
         assertEquals(config.getMaxTokenContext(), context.at("/right/1").asInt());
     }
+    
+    /** AI generated
+     * @throws KustvaktException
+     */
+    @Test
+    public void testCutTokenContextLarge ()
+            throws KustvaktException {
+        // add test user to LargeContextGroup via web service
+        Form form = new Form();
+        form.param("members", TEST_USER);
+        Response addResponse = target().path(API_VERSION).path("group")
+                .path("@" + LARGE_CONTEXT_GROUP).path("member").request()
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(
+                                LARGE_CONTEXT_GROUP_ADMIN, "pass"))
+                .put(Entity.form(form));
+        assertEquals(Status.OK.getStatusCode(), addResponse.getStatus());
+
+        try {
+            Response searchResponse = target().path(API_VERSION).path("search")
+                    .queryParam("q", "Sonne")
+                    .queryParam("ql", "poliqarp")
+                    .queryParam("context", "60-token,60-token")
+                    .request()
+                    .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                            .createBasicAuthorizationHeaderValue(
+                                    TEST_USER, "pass"))
+                    .get();
+            String ent = searchResponse.readEntity(String.class);
+            JsonNode node = JsonUtils.readTree(ent);
+
+            JsonNode context = node.at("/meta/context");
+            assertEquals(config.getMaxTokenContextLarge(),
+                    context.at("/left/1").asInt());
+            assertEquals(config.getMaxTokenContextLarge(),
+                    context.at("/right/1").asInt());
+
+            // match context
+            context = node.at("/matches/0/context");
+            assertEquals(config.getMaxTokenContextLarge(),
+                    context.at("/left/1").asInt());
+            assertEquals(config.getMaxTokenContextLarge(),
+                    context.at("/right/1").asInt());
+        }
+        finally {
+            // remove test user from group via web service
+            Response deleteResponse = target().path(API_VERSION).path("group")
+                    .path("@" + LARGE_CONTEXT_GROUP)
+                    .path("~" + TEST_USER).request()
+                    .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                            .createBasicAuthorizationHeaderValue(
+                                    LARGE_CONTEXT_GROUP_ADMIN, "pass"))
+                    .delete();
+            assertEquals(Status.OK.getStatusCode(), deleteResponse.getStatus());
+        }
+    }
 
     @Test
     public void testMetaRewrite () throws KustvaktException {
@@ -79,6 +144,7 @@
         assertEquals("right", context.at("/rewrites/1/scope").asText());
         assertEquals("token", context.at("/rewrites/1/original/0").asText());
         assertEquals(60, context.at("/rewrites/1/original/1").asInt());
-        
     }
+
+    
 }
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerAdminTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerAdminTest.java
index 7982294..cd1ce37 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerAdminTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerAdminTest.java
@@ -164,8 +164,8 @@
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         String entity = response.readEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(2, node.size());
-        assertEquals("HIDDEN", node.get(0).at("/status").asText());
+        assertEquals(3, node.size());
+        assertEquals("HIDDEN", node.get(1).at("/status").asText());
 
         deleteGroupByName(doryGroupName, "dory");
     }
diff --git a/src/test/resources/kustvakt-test.conf b/src/test/resources/kustvakt-test.conf
index bb8997d..9cd1a9e 100644
--- a/src/test/resources/kustvakt-test.conf
+++ b/src/test/resources/kustvakt-test.conf
@@ -49,6 +49,8 @@
 # Virtual corpus and queries
 max.user.persistent.queries = 5
 max.token.context.size = 40
+max.token.context.size.large = 50
+
 # default false
 vc.list.statistics.enabled = true