/*
 * #%L
 * Alfresco Remote API
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.web.scripts.audit;

import java.net.URL;
import java.util.Date;
import java.util.Map;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.audit.AuditComponent;
import org.alfresco.repo.audit.AuditServiceImpl;
import org.alfresco.repo.audit.UserAuditFilter;
import org.alfresco.repo.audit.model.AuditModelRegistryImpl;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.web.scripts.BaseWebScriptTest;
import org.alfresco.service.cmr.audit.AuditService;
import org.alfresco.service.cmr.audit.AuditService.AuditApplication;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.GUID;
import org.alfresco.util.testing.category.LuceneTests;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;
import org.springframework.extensions.surf.util.ISO8601DateFormat;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.TestWebScriptServer;
import org.springframework.extensions.webscripts.TestWebScriptServer.Response;
import org.springframework.util.ResourceUtils;

/**
 * Test the audit web scripts
 * 
 * @author Derek Hulley
 * @since 3.4
 */
@Category({OwnJVMTestsCategory.class, LuceneTests.class})
public class AuditWebScriptTest extends BaseWebScriptTest
{
    private static final String APP_REPOTEST_NAME = "AlfrescoRepositoryTest";
    private static final String APP_REPOTEST_PATH = "/repositorytest";
    private static final String APP_SEARCHTEST_NAME = "SearchAudit";
    private static final String APP_SEARCHTEST_PATH = "/searchaudit";

    private ApplicationContext ctx;
    private AuditService auditService;
    private SearchService searchService;
    private AuthenticationService authenticationService;
    private FileFolderService fileFolderService;
    private Repository repositoryHelper;
    
    private String admin;
    private boolean wasGloballyEnabled;
    boolean wasRepoEnabled;
    private boolean wasSearchEnabled;
    
    private NodeRef testRoot;
    
    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        ctx = getServer().getApplicationContext();
        //MNT-10807 : Auditing does not take into account audit.filter.alfresco-access.transaction.user
        UserAuditFilter userAuditFilter = new UserAuditFilter();
        userAuditFilter.setUserFilterPattern("System;.*");
        userAuditFilter.afterPropertiesSet();
        AuditComponent auditComponent = (AuditComponent) ctx.getBean("auditComponent");
        auditComponent.setUserAuditFilter(userAuditFilter);
        AuditServiceImpl auditServiceImpl = (AuditServiceImpl) ctx.getBean("auditService");
        auditServiceImpl.setAuditComponent(auditComponent);
        authenticationService = (AuthenticationService) ctx.getBean("AuthenticationService");
        auditService = (AuditService) ctx.getBean("AuditService");
        searchService = (SearchService) ctx.getBean("SearchService");
        repositoryHelper = (Repository)getServer().getApplicationContext().getBean("repositoryHelper");
        fileFolderService = (FileFolderService)getServer().getApplicationContext().getBean("FileFolderService");
        admin = AuthenticationUtil.getAdminUserName();

        // Register the test models
        AuditModelRegistryImpl auditModelRegistry = (AuditModelRegistryImpl) ctx.getBean("auditModel.modelRegistry");
        URL testModelUrl = ResourceUtils.getURL("classpath:alfresco/testaudit/alfresco-audit-test-repository.xml");
        URL testModelUrl1 = ResourceUtils.getURL("classpath:alfresco/testaudit/alfresco-audit-test-mnt-16748.xml");
        auditModelRegistry.registerModel(testModelUrl);
        auditModelRegistry.registerModel(testModelUrl1);
        auditModelRegistry.loadAuditModels();
        
        AuthenticationUtil.setFullyAuthenticatedUser(admin);
        
        wasGloballyEnabled = auditService.isAuditEnabled();
        wasRepoEnabled = auditService.isAuditEnabled(APP_REPOTEST_NAME, APP_REPOTEST_PATH);
        wasSearchEnabled = auditService.isAuditEnabled(APP_SEARCHTEST_NAME, APP_SEARCHTEST_PATH);
        // Only enable if required
        if (!wasGloballyEnabled)
        {
            auditService.setAuditEnabled(true);
            wasGloballyEnabled = auditService.isAuditEnabled();
            if (!wasGloballyEnabled)
            {
                fail("Failed to enable global audit for test");
            }
        }
        if (!wasRepoEnabled)
        {
            auditService.enableAudit(APP_REPOTEST_NAME, APP_REPOTEST_PATH);
            wasRepoEnabled = auditService.isAuditEnabled(APP_REPOTEST_NAME, APP_REPOTEST_PATH);
            if (!wasRepoEnabled)
            {
                fail("Failed to enable repo audit for test");
            }
        }
        if (!wasSearchEnabled)
        {
            auditService.enableAudit(APP_SEARCHTEST_NAME, APP_SEARCHTEST_PATH);
            wasSearchEnabled = auditService.isAuditEnabled(APP_SEARCHTEST_NAME, APP_SEARCHTEST_PATH);
            if (!wasSearchEnabled)
            {
                fail("Failed to enable search audit for test");
            }
        }
    }

    @Override
    protected void tearDown() throws Exception
    {
        super.tearDown();
        // Leave audit in correct state
        try
        {
            if (!wasGloballyEnabled)
            {
                auditService.setAuditEnabled(false);
            }
        }
        catch (Throwable e)
        {
            throw new RuntimeException("Failed to set audit back to globally enabled/disabled state", e);
        }
        try
        {
            if (wasRepoEnabled)
            {
                auditService.enableAudit(APP_REPOTEST_NAME, APP_REPOTEST_PATH);
            }
            else
            {
                auditService.disableAudit(APP_REPOTEST_NAME, APP_REPOTEST_PATH);
            }
        }
        catch (Throwable e)
        {
            throw new RuntimeException("Failed to set repo audit back to enabled/disabled state", e);
        }
        try
        {
            if (wasSearchEnabled)
            {
                auditService.enableAudit(APP_SEARCHTEST_NAME, APP_SEARCHTEST_PATH);
            }
            else
            {
                auditService.disableAudit(APP_SEARCHTEST_NAME, APP_SEARCHTEST_PATH);
            }
        }
        catch (Throwable e)
        {
            throw new RuntimeException("Failed to set search audit back to enabled/disabled state", e);
        }
    }
    
    public void testGetWithoutPermissions() throws Exception
    {
        String url = "/api/audit/control";
        TestWebScriptServer.GetRequest req = new TestWebScriptServer.GetRequest(url);
        sendRequest(req, 401, AuthenticationUtil.getGuestRoleName());
    }
    
    public void testGetIsAuditEnabledGlobally() throws Exception
    {
        boolean wasEnabled = auditService.isAuditEnabled();
        Map<String, AuditApplication> checkApps = auditService.getAuditApplications();

        String url = "/api/audit/control";
        TestWebScriptServer.GetRequest req = new TestWebScriptServer.GetRequest(url);
        
        Response response = sendRequest(req, Status.STATUS_OK, admin);
        JSONObject json = new JSONObject(response.getContentAsString());
        boolean enabled = json.getBoolean(AbstractAuditWebScript.JSON_KEY_ENABLED);
        assertEquals("Mismatched global audit enabled", wasEnabled, enabled);
        JSONArray apps = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_APPLICATIONS);
        assertEquals("Incorrect number of applications reported", checkApps.size(), apps.length());
    }
    
    public void testGetIsAuditEnabledMissingApp() throws Exception
    {
        String url = "/api/audit/control/xxx";
        TestWebScriptServer.GetRequest req = new TestWebScriptServer.GetRequest(url);
        
        sendRequest(req, 404, admin);
    }
    
    public void testSetAuditEnabledGlobally() throws Exception
    {
        boolean wasEnabled = auditService.isAuditEnabled();

        if (wasEnabled)
        {
            String url = "/api/audit/control?enable=false";
            TestWebScriptServer.PostRequest req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON);
            sendRequest(req, Status.STATUS_OK, admin);
        }
        else
        {
            String url = "/api/audit/control?enable=true";
            TestWebScriptServer.PostRequest req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON);
            sendRequest(req, Status.STATUS_OK, admin);
        }
        
        // Check that it worked
        testGetIsAuditEnabledGlobally();
    }
    
    public void testGetIsAuditEnabledRepo() throws Exception
    {
        boolean wasEnabled = auditService.isAuditEnabled(APP_REPOTEST_NAME, null);

        String url = "/api/audit/control/" + APP_REPOTEST_NAME + APP_REPOTEST_PATH;
        TestWebScriptServer.GetRequest req = new TestWebScriptServer.GetRequest(url);

        if (wasEnabled)
        {
            Response response = sendRequest(req, Status.STATUS_OK, admin);
            JSONObject json = new JSONObject(response.getContentAsString());
            JSONArray apps = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_APPLICATIONS);
            assertEquals("Incorrect number of applications reported", 1, apps.length());
            JSONObject app = apps.getJSONObject(0);
            String appName = app.getString(AbstractAuditWebScript.JSON_KEY_NAME);
            String appPath = app.getString(AbstractAuditWebScript.JSON_KEY_PATH);
            boolean appEnabled = app.getBoolean(AbstractAuditWebScript.JSON_KEY_ENABLED);
            assertEquals("Mismatched application audit enabled", wasEnabled, appEnabled);
            assertEquals("Mismatched application audit name", APP_REPOTEST_NAME, appName);
            assertEquals("Mismatched application audit path", APP_REPOTEST_PATH, appPath);
        }
    }

    public void testGetAuditSearchService() throws Exception
    {
        // Delete search audit entries (if any)
        String url = "/api/audit/clear/" + APP_SEARCHTEST_NAME;
        TestWebScriptServer.PostRequest postReq = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON);
        Response response = sendRequest(postReq, Status.STATUS_OK, admin);
        JSONObject json = new JSONObject(response.getContentAsString());
        assertTrue(json.getInt(AbstractAuditWebScript.JSON_KEY_CLEARED) >= 0);

        // create a file
        this.testRoot = this.repositoryHelper.getCompanyHome();
        String filename = "test_doc" + GUID.generate() + ".txt";
        NodeRef testFile = this.fileFolderService.create(this.testRoot, filename, ContentModel.TYPE_CONTENT).getNodeRef();

        // search the newly created file
        SearchParameters sp = new SearchParameters();
        sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO);
        sp.setQuery("=cm:name:" + filename);
        sp.addStore(testFile.getStoreRef());
        searchService.query(sp);

        // construct the get audit request
        url = "/api/audit/query/" + APP_SEARCHTEST_NAME + "/searchaudit/queryX/searchParametersX?verbose=true";
        TestWebScriptServer.GetRequest getReq = new TestWebScriptServer.GetRequest(url);

        response = sendRequest(getReq, Status.STATUS_OK, admin);
        json = new JSONObject(response.getContentAsString());

        JSONArray jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
        assertEquals("Incorrect number of entries reported", 1, jsonEntries.length());

        JSONObject values = (JSONObject) ((JSONObject) jsonEntries.get(0)).get(AbstractAuditWebScript.JSON_KEY_ENTRY_VALUES);
        assertTrue("Audit entry was not found", values.toString(0).contains("query==cm:name:" + filename));

        // clear audit entries for the application
        auditService.clearAudit(APP_SEARCHTEST_NAME, null, null);
    }

    public void testSetAuditEnabledRepo() throws Exception
    {
        boolean wasEnabled = auditService.isAuditEnabled(APP_REPOTEST_NAME, APP_REPOTEST_PATH);

        if (wasEnabled)
        {
            String url = "/api/audit/control/" + APP_REPOTEST_NAME + APP_REPOTEST_PATH + "?enable=false";
            TestWebScriptServer.PostRequest req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON);
            sendRequest(req, Status.STATUS_OK, admin);
        }
        else
        {
            String url = "/api/audit/control/" + APP_REPOTEST_NAME + APP_REPOTEST_PATH + "?enable=true";
            TestWebScriptServer.PostRequest req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON);
            sendRequest(req, Status.STATUS_OK, admin);
        }
        
        // Check that it worked
        testGetIsAuditEnabledRepo();
    }
    
    /**
     * Perform a failed login attempt
     */
    private void loginWithFailure(final String username) throws Exception
    {
        // Force a failed login
        RunAsWork<Void> failureWork = new RunAsWork<Void>()
        {
            @Override
            public Void doWork() throws Exception
            {
                try
                {
                    authenticationService.authenticate(username, "crud".toCharArray());
                    fail("Failed to force authentication failure");
                }
                catch (AuthenticationException e)
                {
                    // Expected
                }
                return null;
            }
        };
        AuthenticationUtil.runAs(failureWork, AuthenticationUtil.getSystemUserName());
    }
    
    public synchronized void testClearAuditRepo() throws Exception
    {
        long now = System.currentTimeMillis() - 10L;        // Accuracy can be a problem
        long future = Long.MAX_VALUE;
        
        loginWithFailure(getName());
        
        // Wait for the background thread to run
        try
        {
            this.wait(100);
        }
        catch (Throwable e)
        {
        }
        
        // Delete audit entries that could not have happened
        String url = "/api/audit/clear/" + APP_REPOTEST_NAME + "?fromTime=" + future;
        TestWebScriptServer.PostRequest req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON);
        Response response = sendRequest(req, Status.STATUS_OK, admin);
        JSONObject json = new JSONObject(response.getContentAsString());
        int cleared = json.getInt(AbstractAuditWebScript.JSON_KEY_CLEARED);
        assertEquals("Could not have cleared more than 0", 0, cleared);
        
        // ALF-3055 : auditing of failures is now asynchronous, so loop 60 times with a
        // 1 second sleep to ensure that the audit is processed
        for(int i = 0; i < 60; i++)
        {
            // Delete the entry (at least)
            url = "/api/audit/clear/" + APP_REPOTEST_NAME + "?fromTime=" + now + "&toTime=" + future;
            req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON);
            response = sendRequest(req, Status.STATUS_OK, admin);
            json = new JSONObject(response.getContentAsString());
            cleared = json.getInt(AbstractAuditWebScript.JSON_KEY_CLEARED);
            if (cleared > 0)
            {
                break;
            }
            
            Thread.sleep(1000);
        }
        assertTrue("Should have cleared at least 1 entry", cleared > 0);

        // Delete all entries
        url = "/api/audit/clear/" + APP_REPOTEST_NAME;;
        req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON);
        response = sendRequest(req, Status.STATUS_OK, admin);
        json = new JSONObject(response.getContentAsString());
        cleared = json.getInt(AbstractAuditWebScript.JSON_KEY_CLEARED);
    }
    
    @SuppressWarnings("unused")
    public void testQueryAuditRepo() throws Exception
    {
        long now = System.currentTimeMillis();
        long future = Long.MAX_VALUE;
        
        auditService.setAuditEnabled(true);
        auditService.enableAudit(APP_REPOTEST_NAME, APP_REPOTEST_PATH);

        loginWithFailure(getName());
        
        // Query for audit entries that could not have happened
        String url = "/api/audit/query/" + APP_REPOTEST_NAME + "?fromTime=" + now + "&verbose=true";
        JSONArray jsonEntries = null;
        TestWebScriptServer.GetRequest req = null;
        Response response = null;
        JSONObject json = null;
        Long entryCount = null;
        // ALF-3055 : auditing of failures is now asynchronous, so loop 60 times with a
        // 1 second sleep to ensure that the audit is processed
        for(int i = 0; i < 60; i++)
        {
	        req = new TestWebScriptServer.GetRequest(url);
	        response = sendRequest(req, Status.STATUS_OK, admin);
	        json = new JSONObject(response.getContentAsString());
	        entryCount = json.getLong(AbstractAuditWebScript.JSON_KEY_ENTRY_COUNT);
	        jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);

	        if(jsonEntries.length() > 0)
	        {
	        	break;
	        }
        	Thread.sleep(1000);
        }
        assertTrue("Expected at least one entry", jsonEntries.length() > 0);
        assertEquals("Entry count and physical count don't match", new Long(jsonEntries.length()), entryCount);
        JSONObject jsonEntry = jsonEntries.getJSONObject(0);
        Long entryId = jsonEntry.getLong(AbstractAuditWebScript.JSON_KEY_ENTRY_ID);
        assertNotNull("No entry ID", entryId);
        String entryTimeStr = jsonEntry.getString(AbstractAuditWebScript.JSON_KEY_ENTRY_TIME);
        assertNotNull("No entry time String", entryTimeStr);
        Date entryTime = ISO8601DateFormat.parse((String)entryTimeStr); // Check conversion
        JSONObject jsonValues = jsonEntry.getJSONObject(AbstractAuditWebScript.JSON_KEY_ENTRY_VALUES);
        String entryUsername = jsonValues.getString("/repositorytest/login/error/user");
        assertEquals("Didn't find the login-failure-user", getName(), entryUsername);
        
        // Query using well-known ID
        Long fromEntryId = entryId;                 // Search is inclusive on the 'from' side
        Long toEntryId = entryId.longValue() + 1L;  // Search is exclusive on the 'to' side
        url = "/api/audit/query/" + APP_REPOTEST_NAME + "?fromId=" + fromEntryId + "&toId=" + toEntryId;
        req = new TestWebScriptServer.GetRequest(url);
        response = sendRequest(req, Status.STATUS_OK, admin);
        json = new JSONObject(response.getContentAsString());
        jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
        assertEquals("Incorrect number of search results", 1, jsonEntries.length());
        
        // Query using a non-existent entry path
        url = "/api/audit/query/" + APP_REPOTEST_NAME + "/repositorytest/login/error/userXXX" + "?verbose=true";
        req = new TestWebScriptServer.GetRequest(url);
        response = sendRequest(req, Status.STATUS_OK, admin);
        json = new JSONObject(response.getContentAsString());
        jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
        assertTrue("Should not have found anything", jsonEntries.length() == 0);
        
        // Query using a good entry path
        url = "/api/audit/query/" + APP_REPOTEST_NAME + "/repositorytest/login/error/user" + "?verbose=true";
        req = new TestWebScriptServer.GetRequest(url);
        response = sendRequest(req, Status.STATUS_OK, admin);
        json = new JSONObject(response.getContentAsString());
        jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
        assertTrue("Should have found entries", jsonEntries.length() > 0);
        
        // Now login with failure using a GUID and ensure that we can find it
        String missingUser = new Long(System.currentTimeMillis()).toString();

        // Query for event that has not happened
        url = "/api/audit/query/" + APP_REPOTEST_NAME + "/repositorytest/login/error/user" + "?value=" + missingUser;
        req = new TestWebScriptServer.GetRequest(url);
        response = sendRequest(req, Status.STATUS_OK, admin);
        json = new JSONObject(response.getContentAsString());
        jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
        assertEquals("Incorrect number of search results", 0, jsonEntries.length());
        
        loginWithFailure(missingUser);
        
        // Query for event that has happened once
        url = "/api/audit/query/" + APP_REPOTEST_NAME + "/repositorytest/login/error/user" + "?value=" + missingUser;
        // ALF-3055 : auditing of failures is now asynchronous, so loop 60 times with a
        // 1 second sleep to ensure that the audit is processed
        for(int i = 0; i < 60; i++)
        {
	        req = new TestWebScriptServer.GetRequest(url);
	        response = sendRequest(req, Status.STATUS_OK, admin);
	        json = new JSONObject(response.getContentAsString());
	        jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
	        if(jsonEntries.length() == 1)
	        {
	        	break;
	        }
        	Thread.sleep(1000);
        }
        assertEquals("Incorrect number of search results", 1, jsonEntries.length());
        
        // Query for event, but casting the value to the incorrect type
        url = "/api/audit/query/" + APP_REPOTEST_NAME + "/repositorytest/login/error/user" + "?value=" + missingUser + "&valueType=java.lang.Long";
        req = new TestWebScriptServer.GetRequest(url);
        response = sendRequest(req, Status.STATUS_OK, admin);
        json = new JSONObject(response.getContentAsString());
        jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
        assertEquals("Incorrect number of search results", 0, jsonEntries.length());
        
        // Test what happens when the target data needs encoding
        now = System.currentTimeMillis();
        
        String oddUser = "%$£\\\"\'";
        loginWithFailure(oddUser);
        
        // Query for the event limiting to one by count and descending (i.e. get last)
        url = "/api/audit/query/" + APP_REPOTEST_NAME + "?forward=false&limit=1&verbose=true&fromTime=" + now;
        // ALF-3055 : auditing of failures is now asynchronous, so loop 60 times with a
        // 1 second sleep to ensure that the audit is processed
        for(int i = 0; i < 60; i++)
        {
            req = new TestWebScriptServer.GetRequest(url);
            response = sendRequest(req, Status.STATUS_OK, admin);
            json = new JSONObject(response.getContentAsString());
            jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
	        if(jsonEntries.length() == 1)
	        {
	        	break;
	        }
        	Thread.sleep(1000);
        }
        assertEquals("Incorrect number of search results", 1, jsonEntries.length());

        jsonEntry = jsonEntries.getJSONObject(0);
        entryId = jsonEntry.getLong(AbstractAuditWebScript.JSON_KEY_ENTRY_ID);
        assertNotNull("No entry ID", entryId);
        jsonValues = jsonEntry.getJSONObject(AbstractAuditWebScript.JSON_KEY_ENTRY_VALUES);
        entryUsername = jsonValues.getString("/repositorytest/login/error/user");
        assertEquals("Didn't find the login-failure-user", oddUser, entryUsername);
    }
}