package be.iminds.ilabt.jfed.lowlevel.api.test;

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.api.*;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnectionProvider;
import be.iminds.ilabt.jfed.lowlevel.connection.SfaConnection;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.testing.base.ApiTestResult;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.RFC3339Util;
import be.iminds.ilabt.jfed.util.TargetAuthority;

import javax.inject.Inject;
import java.text.ParseException;
import java.util.*;

/**
 * TestFederationSliceAuthority2
 */
public class TestFederationSliceAuthority2 extends AbstractFederationApi2Test {
    @Inject
    public TestFederationSliceAuthority2(Logger logger, TargetAuthority testedAuthority, GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider, TestbedInfoSource testbedInfoSource, JFedPreferences jFedPreferences) {
        super(logger, testedAuthority, geniUserProvider, connectionProvider, testbedInfoSource, jFedPreferences);
    }

    public String getTestDescription() {
        return "Test Federation API Slice Authority version 2";
    }

    private FederationSliceAuthorityApi2 sa;
    private FederationMemberAuthorityApi2 ma;

    @Override
    protected List<String> getOptConfKeys() {
        return Collections.singletonList("modify_project_membership_test_new_role");
    }

    public SfaConnection getSAConnection() throws JFedException {
        return (SfaConnection) connectionProvider.getConnectionByAuthority(user, testedAuthority.getServerToConnect(),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_CH_SA, 2));
    }

    public SfaConnection getMAConnection() throws JFedException {
        return (SfaConnection) connectionProvider.getConnectionByAuthority(user, testedAuthority.getServerToConnect(),
                new ApiInfo.Api(ApiInfo.ApiName.GENI_CH_MA, 2));
    }

    final List<String> sliceDefaultFieldNames = new ArrayList<>();
    List<AbstractFederationApi2.GetVersionResult.FieldInfo> sliceDefaultFields =
            new ArrayList<>();
    final List<String> projectDefaultFieldNames = new ArrayList<>();
    List<AbstractFederationApi2.GetVersionResult.FieldInfo> projectDefaultFields =
            new ArrayList<>();

    public void setUp() {

        sa = new FederationSliceAuthorityApi2(logger, jFedPreferences);
        ma = new FederationMemberAuthorityApi2(logger, jFedPreferences);
        sa.setHandleMalformedReplies(false); //throw exceptions instead of trying to recover from malformed replies
        ma.setHandleMalformedReplies(false); //throw exceptions instead of trying to recover from malformed replies

        sliceDefaultFields = sa.getMinimumFields("SLICE");
        projectDefaultFields = sa.getMinimumFields("PROJECT");
        for (AbstractFederationApi2.GetVersionResult.FieldInfo fi : sliceDefaultFields)
            sliceDefaultFieldNames.add(fi.getName());
        for (AbstractFederationApi2.GetVersionResult.FieldInfo fi : projectDefaultFields)
            projectDefaultFieldNames.add(fi.getName());
    }


    private boolean hasSliceMemberService = false;
    private boolean hasSliverInfoService = false;
    private boolean hasProjectService = false;
    private boolean hasProjectMemberService = false;

    private List<String> allRoles;

    @Test(groups = {"get_version", "slice", "sliver_info", "slice_member"})
    public void getVersion() throws JFedException {
        AbstractFederationApi2.FederationApiReply<FederationSliceAuthorityApi2.GetVersionSAResult> reply =
                sa.getVersion(getSAConnection());
        checkGetVersion(reply, sa, true);

        Map<String, Object> htRes = assertStringObjectMap(reply.getRawValue());
        List services = assertMapContainsList(htRes, "SERVICES");

        if (services != null) {
            hasSliceMemberService = services.contains("SLICE_MEMBER");
            hasSliverInfoService = services.contains("SLIVER_INFO");
            hasProjectService = services.contains("PROJECT");
            hasProjectMemberService = services.contains("PROJECT_MEMBER");
        }

        allRoles = reply.getValue().getRoles();

        warnIfNot(allRoles.size() > 0, "No roles are defined in get_version");
    }


    AnyCredential maTestUserCredential;
    List<AnyCredential> maTestUserCredentialList;

    @Test(softDepends = {"getVersion"}, groups = {"get_credential"})
    public void getTestUserCredential() throws JFedException {
        GeniUrn testUserUrn = GeniUrn.parse(user.getUserUrnString());
        assertNotNull(testUserUrn, "Error in test user urn: " + user.getUserUrnString());

        AbstractFederationApi2.FederationApiReply<List<AnyCredential>> reply =
                ma.getCredentials(getMAConnection(), new ArrayList<>(), testUserUrn, null);
        checkCorrectnessXmlRpcResult(reply);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        List<AnyCredential> testUserCreds = reply.getValue();
        assertNotNull(testUserCreds, "no credential returned");
        assertNotEmpty(testUserCreds,
                "empty list of credentials returned. Expecting a list with credentials, but got  " + (
                        reply.getRawValue() == null ?
                                "null" :
                                (reply.getRawValue() instanceof Object[] ?
                                        ("a list: " + reply.getRawValue()) :
                                        ("something of class:" + reply.getRawValue().getClass().getName())
                                )
                )
        );
        maTestUserCredentialList = new ArrayList<>(testUserCreds);
        maTestUserCredential = testUserCreds.get(0);
    }

    HashMap<String, Object> testUserInfo = null;

    @Test(softDepends = {
            "getTestUserCredential"}, description = "Some methods might require user info, so we try to fetch all user info here. " +
            "Any failure here is not a failure of the SA, but a failure in the MA.", groups = {"lookup_member"})
    public void getTestUserInfo() throws JFedException {
        GeniUrn testUserUrn = GeniUrn.parse(user.getUserUrnString());
        assertNotNull(testUserUrn, "Error in test user urn: " + user.getUserUrnString());
        assert testUserUrn != null;

        Map<String, String> match = Collections.singletonMap("MEMBER_URN", testUserUrn.getValue());

        setErrorsNotFatal();
        AbstractFederationApi2.FederationApiReply<FederationMemberAuthorityApi2.MemberInfoList> reply =
                ma.lookupMember(getMAConnection(), maTestUserCredentialList, match, null, null);
        checkCorrectnessXmlRpcResult(reply);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        Map<String, Object> ht = assertStringObjectMap(reply.getRawValue());
        if (ht != null) {
            Map<String, Object> replyUserInfo = assertStringObjectMap(ht.get(testUserUrn.getValue()));
            if (replyUserInfo != null) {
                testUserInfo = new HashMap<>();
                testUserInfo.putAll(replyUserInfo);
            }
        }


        note("Retrieved user info fields: " + (testUserInfo == null ? "null" : testUserInfo.keySet()));

        setErrorsFatal();
    }

    AnyCredential testUserCredential;
    List<AnyCredential> testUserCredentialList;

    @Test(softDepends = {"getTestUserCredential"}, groups = {
            "get_credential"}, description = "Get A Credential for the test user. If the MA get_credential call did not work, this will try to fall back to the old PROTOGENI_SA API.")
    public void retrieveCredentialSomehow() throws JFedException {
        if (maTestUserCredentialList != null && maTestUserCredential != null) {
            testUserCredential = maTestUserCredential;
            testUserCredentialList = maTestUserCredentialList;
        } else {
            if (!ApiInfo.hasService(user.getUserAuthorityServer(), ApiInfo.ApiName.PROTOGENI_SA, 1)) {
                errorFatal("The Uniform Federation API Member Authority get_credential call failed to provide a credential. additionally, the test user authority does not have a PROTOGENI_SA available as fallback. => Cannot retrieve a credential for the test user.");
            } else {
                note("The Uniform Federation API Member Authority get_credential call failed to provide a credential: Falling back to the PROTOGENI_SA API to retrieve a credential for the remaining tests.");
                assert user.getUserAuthorityServer() != null;
                SfaConnection protogeniSaCon =
                        (SfaConnection) connectionProvider.getConnectionByAuthority(user,
                                user.getUserAuthorityServer(), new ApiInfo.Api(ApiInfo.ApiName.PROTOGENI_SA, 1));
                ProtogeniSliceAuthority protogeniSa = new ProtogeniSliceAuthority(logger, jFedPreferences);

                ProtogeniSliceAuthority.SliceAuthorityReply<AnyCredential> pgSaReply =
                        protogeniSa.getCredential(protogeniSaCon);
                assertTrue(pgSaReply.getGeniResponseCode().isSuccess());
                AnyCredential pgCred = pgSaReply.getValue();

                assertNotNull(pgCred, "no credential returned by protogeni SA");
                testUserCredentialList = new ArrayList<>();
                testUserCredentialList.add(pgCred);
                testUserCredential = pgCred;
            }
        }
    }


    private GeniUrn sliceUrn;
    private static final String INITIAL_SLICE_DESCRIPTION = "test slice used in automated testing";

    @Test(hardDepends = {"retrieveCredentialSomehow"}, softDepends = {"getVersion", "findProjectToUseInTests",
                                                                      "getTestUserInfo"}, groups = {"slice"})
    public void createSlice() throws JFedException {
        String sliceName =
                CommonAMTest.createSliceName(this, user.getUserAuthorityServer(), ApiInfo.getDefaultComponentManagerForEach(testedAuthority.getServerForRspecComponentManager()), "S", 3, null);

        Map<String, String> fields = new HashMap<>();
        fields.put("SLICE_EXPIRATION", RFC3339Util.dateToRFC3339String(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000), true, true, true));
        fields.put("SLICE_DESCRIPTION", INITIAL_SLICE_DESCRIPTION);

        //if PROJECT SERVICE is available, we NEED to add SLICE_PROJECT_URN to the keys
        if (hasProjectService) {
            if (projectUrn != null)
                fields.put("SLICE_PROJECT_URN", projectUrn.getValue());
            else
                skip("No PROJECT could be created or found, but craete SLICE requires a project: skipping test.");
        }
        if (getVersionResult != null && getVersionResult.getFieldsForObject("PROJECT").containsKey("_GENI_SLICE_EMAIL")) {
            if (testUserInfo != null && testUserInfo.containsKey("MEMBER_EMAIL"))
                fields.put("_GENI_SLICE_EMAIL", testUserInfo.get("MEMBER_EMAIL") + "");
            else {
                warn("create_slice requires the field _GENI_SLICE_EMAIL, but the current user's EMAIL is unknown. (the value 'dummy@example.com' will be used, but will likely cause problems)");
                fields.put("_GENI_SLICE_EMAIL", "dummy@example.com");
            }
        }
        addRequiredSupplementaryFieldsToCreate(fields, "SLICE");

        AbstractFederationApi2.FederationApiReply<AbstractFederationApi2.SliceInfo> reply =
                sa.createSlice(getSAConnection(), testUserCredentialList, sliceName, fields, null);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        Map<String, Object> sliceCreationDetails = assertStringObjectMap(reply.getRawValue());
        assertNotNull(sliceCreationDetails);
        note("slice details contains the following fields: " + sliceCreationDetails.keySet());

        setErrorsNotFatal();

        String replySliceUrn = assertMapContainsString(sliceCreationDetails, "SLICE_URN");
        note("SLICE_URN of created slice: \"" + replySliceUrn + "\"");
        sliceUrn = GeniUrn.parse(replySliceUrn);
        assertNotNull(sliceUrn, "SLICE_URN value \"" + replySliceUrn + "\" is not a valid urn");
        assertEquals(sliceUrn.getEncodedResourceType(), "slice", "SLICE_URN is not a slice urn, but is of type " + sliceUrn.getEncodedResourceType());
        assertEquals(sliceUrn.getEncodedResourceName(), sliceName, "SLICE_URN does not have the requested name \"" + sliceName + "\", but has name " + sliceUrn.getEncodedResourceName());
        warnIfNot(sliceUrn.getEncodedTopLevelAuthority().startsWith(testedAuthority.getServerToConnect().getUrnTld()), "SLICE_URN top level authority is unexpected (expected \"" + testedAuthority.getServerToConnect().getUrnTld() + "\" or subauthority): " + sliceUrn.getEncodedTopLevelAuthority());

//        sliceUrn = new GeniUrn(testContext.getTestedAuthority().getUrnTld(), "slice", sliceName);

//        assertMapContainsString(sliceCreationDetails, "SLICE_UID");
//        assertMapContainsString(sliceCreationDetails, "SLICE_EXPIRATION"); //XmlRpc might store Data object!
        Object expirationObject = sliceCreationDetails.get("SLICE_EXPIRATION");
        assertNotNull(expirationObject, "slice creation result Map does not contain key \"SLICE_EXPIRATION\"");
        if ((!(expirationObject instanceof Date)) && (!(expirationObject instanceof String)))
            errorNonFatal("slice creation result Map value for \"SLICE_EXPIRATION\" is not a Date or String but a " + expirationObject.getClass().getName());
        setErrorsFatal();

        //not mandatory
        Object creationSliceCredentialString = sliceCreationDetails.get("SLICE_CREDENTIAL");
        if (creationSliceCredentialString != null) {
            warn("Creating the slice returned a SLICE_CREDENTIAL. This field name is not prefixed with an underscore and is not in the API yet. If it is added, it should probably be of type CREDENTIAL (a list of dictionaries) and not a STRING. Otherwise, the type of credential is not known.");
//            creationSliceCredential = AnyCredential.create("Slice Creation Credential", creationSliceCredentialString, "sfa" , "3"); //assume SFA 3 type...
        }
    }

    private List<AnyCredential> sliceCredentials;

    @Test(hardDepends = {"createSlice"}, groups = {"slice"})
    public void getSliceCredentials() throws JFedException {
        AbstractFederationApi2.FederationApiReply<List<AnyCredential>> reply =
                sa.getSliceCredentials(getSAConnection(), testUserCredentialList, sliceUrn, null);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        sliceCredentials = reply.getValue();
        assertNotNull(sliceCredentials);
        assertNotEmpty(sliceCredentials);
    }


    private void checkSliceLookupResult(AbstractFederationApi2.FederationApiReply<AbstractFederationApi.SliceInfoList> reply, List<String> expectedFieldNames, List<String> excludedFieldNames) {
        Map<String, Object> lookupRes = assertStringObjectMap(reply.getRawValue());
        checkCorrectnessXmlRpcResult(reply);
        checkLookupCorrectness(reply);
        assertNotNull(lookupRes);

        assertTrue(lookupRes.size() > 0, "Lookup result is empty for slice urn: " + sliceUrn.getValue());

        errorNonFatalIfNot(lookupRes.size() == 1, "lookup must return one slice, but it returned " + lookupRes.size());

        Map<String, Object> rawUser = assertStringObjectMap(lookupRes.get(sliceUrn.getValue()));
        errorNonFatalIfNot(rawUser != null, "lookup must return requested slice (" + sliceUrn.getValue() + ") but returned null.");

        //check that if this slice has a slice urn, it matches.
        String rawUrn = rawUser != null ? (String) rawUser.get("SLICE_URN") : null;
        if (rawUrn != null && !Objects.equals(sliceUrn.getValue(), rawUrn))
            errorNonFatal("lookup must return requested slice (" + sliceUrn.getValue() + "), and it did, however its returned SLICE_URN entry is not matching: " + rawUrn);

        for (Object sliceInfo : lookupRes.values()) {
            Map<String, Object> rawSliceInfo = assertStringObjectMap(sliceInfo);
            for (String fieldName : expectedFieldNames) {
                if (!rawSliceInfo.containsKey(fieldName))
                    errorNonFatal("returned result does not contain field: \"" + fieldName + "\"");
            }
            for (String fieldName : excludedFieldNames) {
                if (rawSliceInfo.containsKey(fieldName))
                    errorNonFatal("returned result contains unexpected field: \"" + fieldName + "\"");
            }
        }
    }

    @Test(hardDepends = {"getSliceCredentials"}, groups = {"slice"})
    public void lookupSlicesNoFilter() throws JFedException {
        Map<String, String> match = new HashMap<>();
        match.put("SLICE_URN", sliceUrn.getValue());

        note("looking up slice by SLICE_URN: " + sliceUrn.getValue());
        AbstractFederationApi2.FederationApiReply<AbstractFederationApi.SliceInfoList> reply =
                sa.lookupSlice(getSAConnection(), sliceCredentials, match, null, null);
        checkCorrectnessXmlRpcResult(reply);

        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());


        List<String> expectedFieldNames = new ArrayList<>();
        List<String> excludedFieldNames = new ArrayList<>();
        for (AbstractFederationApi2.GetVersionResult.FieldInfo fi : sliceDefaultFields)
            if (hasProjectService || !Objects.equals(fi.getName(), "SLICE_PROJECT_URN"))   //special field! only present when project service is supported
                expectedFieldNames.add(fi.getName());
        note("No filter provided, so expecting all fields to be returned: " + expectedFieldNames);
        checkSliceLookupResult(reply, expectedFieldNames, excludedFieldNames);

        final Map<String, Object> rawHt = assertStringObjectMap(reply.getRawValue());
        if(rawHt != null) {
            final Map<String, Object> sliceHt = assertStringObjectMap(rawHt.get(sliceUrn.getValue()));
            String desc = assertMapContainsNonemptyString(sliceHt, "SLICE_DESCRIPTION");
            assertEquals(desc, INITIAL_SLICE_DESCRIPTION);
        }
    }

    private static final String updatedSliceDescription =
            "test slice used in automated testing with updated description";

    @Test(hardDepends = {"getSliceCredentials"}, softDepends = {"lookupSlicesNoFilter"}, groups = {"slice"})
    public void updateSlice() throws JFedException {
        Map<String, String> fields = new HashMap<>();
        fields.put("SLICE_DESCRIPTION", updatedSliceDescription);

        AbstractFederationApi2.FederationApiReply<String> reply =
                sa.updateSlice(getSAConnection(), sliceCredentials, sliceUrn, fields, null);
        checkCorrectnessXmlRpcResult(reply);

        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());
    }

    @Test(hardDepends = {"updateSlice"}, groups = {"slice"})
    public void lookupSlicesNoFilterAfterUpdate() throws JFedException {
        Map<String, String> match = new HashMap<>();

        match.put("SLICE_URN", sliceUrn.getValue());

        note("looking up slice by SLICE_URN: " + sliceUrn.getValue());
        AbstractFederationApi2.FederationApiReply<AbstractFederationApi.SliceInfoList> reply =
                sa.lookupSlice(getSAConnection(), sliceCredentials, match, null, null);
        checkCorrectnessXmlRpcResult(reply);

        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        List<String> expectedFieldNames = new ArrayList<>();
        List<String> excludedFieldNames = new ArrayList<>();
        for (AbstractFederationApi2.GetVersionResult.FieldInfo fi : sliceDefaultFields)
            if (hasProjectService || !Objects.equals(fi.getName(), "SLICE_PROJECT_URN"))   //special field! only present when project service is supported
                expectedFieldNames.add(fi.getName());
        note("No filter provided, so expecting all fields to be returned: " + expectedFieldNames);
        checkSliceLookupResult(reply, expectedFieldNames, excludedFieldNames);

        final Map<String, Object> rawHt = assertStringObjectMap(reply.getRawValue());
        if(rawHt != null) {
            final Map<String, Object> sliceHt = assertStringObjectMap(rawHt.get(sliceUrn.getValue()));
            String desc = assertMapContainsNonemptyString(sliceHt, "SLICE_DESCRIPTION");
            assertEquals(desc, updatedSliceDescription);
        }
    }


    ////////////////////////////////////////////////// SLICE_MEMBER methods //////////////////////////////////////////////////

    private String lookupSliceMembersRole;

    @Test(hardDepends = {"createSlice", "getSliceCredentials"}, softDepends = {
            "lookupSlicesNoFilterAfterUpdate"}, groups = {"slice_member"})
    public void lookupSliceMembers() throws JFedException {
        if (!hasSliceMemberService) skip("No SLICE_MEMBER service advertised in get_version");

        AbstractFederationApi2.FederationApiReply<List<FederationSliceAuthorityApi2.UrnRoleTuple>> reply =
                sa.lookupMembers(getSAConnection(), "SLICE", sliceUrn, sliceCredentials, null);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        List<FederationSliceAuthorityApi2.UrnRoleTuple> memberTuples = reply.getValue();
        assertNotNull(memberTuples);
        assertNotEmpty(memberTuples);
        if (projectSuccessFullyCreated)
            assertTrue(memberTuples.size() == 1, "More than 1 member in slice for project created by test: " + memberTuples);

        FederationSliceAuthorityApi2.UrnRoleTuple testUserMemberTuple = null;
        List<String> memberUrns = new ArrayList<>();
        for (FederationSliceAuthorityApi2.UrnRoleTuple curMemberTuple : memberTuples) {
            memberUrns.add(curMemberTuple.getUrn().toString());
            if (Objects.equals(curMemberTuple.getUrn().toString(), user.getUserUrnString()))
                testUserMemberTuple = curMemberTuple;
        }


        assertTrue(memberUrns.contains(user.getUserUrnString()),
                "Expected slice member to be " + user.getUserUrnString() + " but slice members are: " + memberUrns);
        assertNotNull(testUserMemberTuple, "member not found in slice. all member urns: " + memberUrns);
        assert testUserMemberTuple != null;

        note("test user role in slice is: " + testUserMemberTuple.role);
        assertNotNull(testUserMemberTuple.role, "member role is null in slice");
        lookupSliceMembersRole = testUserMemberTuple.role;
    }

    @Test(hardDepends = {"createSlice"}, softDepends = {"lookupSliceMembers"}, groups = {"slice_member"})
    public void lookupSlicesForMember() throws JFedException {
        if (!hasSliceMemberService) skip("No SLICE_MEMBER service advertised in get_version");

        GeniUrn memberUrn = GeniUrn.parse(user.getUserUrnString());
        assertNotNull(memberUrn);

        AbstractFederationApi2.FederationApiReply<List<FederationSliceAuthorityApi2.UrnRoleTuple>> reply =
                sa.lookupForMember(getSAConnection(), "SLICE", memberUrn, testUserCredentialList, null);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        List<FederationSliceAuthorityApi2.UrnRoleTuple> value = reply.getValue();

        assertNotNull(value);
        assertNotEmpty(value);
        assertTrue(value.size() >= 1, "should have been at least 1 slice for " + memberUrn);
        note("Slice urns for member " + memberUrn + ": " + value);

        String role = null;
        for (FederationSliceAuthorityApi2.UrnRoleTuple tuple : value) {
//            note("comparing \""+tuple.getUrn()+"\" with \""+sliceUrn+"\" -> "+tuple.getUrn().equals(sliceUrn));
            if (tuple.getUrn().getEncodedTopLevelAuthority().startsWith(sliceUrn.getEncodedTopLevelAuthority())
                    && Objects.equals(tuple.getUrn().getEncodedResourceName(), sliceUrn.getEncodedResourceName())
                    && Objects.equals(tuple.getUrn().getEncodedResourceType(), "slice"))
                role = tuple.getRole();
            assertEquals(tuple.getUrn().getEncodedResourceType(), "slice", "The returned URN is not a slice urn but is of type: \"" + tuple.getUrn().getEncodedResourceType() + "\"");
        }

        assertNotNull(role, "slice \"" + sliceUrn + "\" was not listed as slice for member \"" + memberUrn + "\". Full result: " + value);
        if (lookupSliceMembersRole != null)
            assertEquals(role, lookupSliceMembersRole, "A different role was returned for \"" + memberUrn + "\" in " + sliceUrn + " for the lookup_slices_for_members and lookup_slice_members calls");
    }

    @Test(hardDepends = {"getVersion", "createSlice", "lookupSliceMembers"}, softDepends = {
            "lookupSlicesForMember"}, groups = {"slice_member"})
    public void modifySliceMembership() throws JFedException {
        assertNotNull(allRoles, "get_version test did not set allRoles");
        assertNotNull(lookupSliceMembersRole, "lookupSliceMembersRole not set in lookupSliceMembers");

        if (!hasSliceMemberService) skip("No SLICE_MEMBER service advertised in get_version");

        List<FederationSliceAuthorityApi2.UrnRoleTuple> membersToAdd =
                new ArrayList<>();
        List<GeniUrn> membersToRemove = new ArrayList<>();
        List<FederationSliceAuthorityApi2.UrnRoleTuple> membersToChange =
                new ArrayList<>();

        if (allRoles.size() <= 1) {
            skip("server does not have multiple roles: cannot test modify_slice_membership");
        }

        String newRole = allRoles.get(0);
        String oldRole = lookupSliceMembersRole;

        if (allRoles.contains("USER") && !Objects.equals(oldRole, "USER")) newRole = "USER";
        if (allRoles.contains("MEMBER") && !Objects.equals(oldRole, "MEMBER")) newRole = "MEMBER";

        for (int i = 1; Objects.equals(newRole, oldRole) && i < allRoles.size(); i++)
            newRole = allRoles.get(i);
        assertNotEquals(newRole, oldRole, "roles defined in get_version do not differ: oldRole=" + oldRole + " allRoles=" + allRoles);

        membersToChange.add(new FederationSliceAuthorityApi2.UrnRoleTuple("SLICE", user.getUserUrn(), newRole));
        note("Wil ltry to change role of member \"" + user.getUserUrnString() + "\" in slice \"" + sliceUrn + "\" from \"" + oldRole + "\" to \"" + newRole + "\"");

        AbstractFederationApi2.FederationApiReply<String> reply1 =
                sa.modifyMembership(getSAConnection(), "SLICE", sliceUrn, membersToAdd, membersToRemove, membersToChange, testUserCredentialList, null);
        checkCorrectnessXmlRpcResult(reply1);

        if (reply1.getOutput() != null && !reply1.getGeniResponseCode().isSuccess() &&
                (reply1.getOutput().toLowerCase().contains("not supported") || reply1.getOutput().toLowerCase().contains("not implemented")))
            skip("This server does not support this method");

        assertTrue(reply1.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply1.getGeniResponseCode());


        //lookup and check if changed correctly
        AbstractFederationApi2.FederationApiReply<List<FederationSliceAuthorityApi2.UrnRoleTuple>> reply2 =
                sa.lookupMembers(getSAConnection(), "SLICE", sliceUrn, testUserCredentialList, null);
        assertTrue(reply2.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply2.getGeniResponseCode());

        List<FederationSliceAuthorityApi2.UrnRoleTuple> memberTuples = reply2.getValue();
        assertNotNull(memberTuples);
        assertNotEmpty(memberTuples);
        assertTrue(memberTuples.size() == 1, "Expected exactly 1 member in slice: " + memberTuples);

        FederationSliceAuthorityApi2.UrnRoleTuple memberTuple = memberTuples.get(0);
        assertEquals(memberTuple.urn.getValue(), user.getUserUrnString(), "Expected slice member to be " + user.getUserUrnString() + " but it was " + memberTuple.urn.getValue());
        note("test user role in slice is: " + memberTuple.role);
        assertNotNull(memberTuple.role, "member role is null in slice");
        assertEquals(memberTuple.role, newRole, "member role differs from newly assigned role");
        lookupSliceMembersRole = memberTuple.role;
    }


    ////////////////////////////////////////////////// SLIVER_INFO methods //////////////////////////////////////////////////

    @Test(hardDepends = {"retrieveCredentialSomehow", "createSlice", "getSliceCredentials"},
            softDepends = {"lookupSlicesNoFilterAfterUpdate", "lookupSlicesForMember", "lookupSliceMembers",
                           "modifySliceMembership"},
            groups = {"sliver_info"})
    public void lookupSliverInfoNoFilter() throws JFedException {
        if (!hasSliverInfoService) skip("No SLIVER_INFO service advertised in get_version");

        Map<String, String> match = new HashMap<>();
        List<String> filter = null;// new List<String>();

        AbstractFederationApi.FederationApiReply<FederationSliceAuthorityApi2.SliverInfoList>
                reply =
                sa.lookupSliverInfo(getSAConnection(), sliceCredentials/*testUserCredentialList*/, sliceUrn, match, filter, null);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        checkLookupCorrectness(reply);
        FederationSliceAuthorityApi2.SliverInfoList lookupRes = reply.getValue();
        assertNotNull(lookupRes);
        assertTrue(lookupRes.size() == 0);

        note("This test just checks that there are no slivers in the newly created slice.");
    }

    private GeniUrn createdSliverUrn;
    private GeniUrn createdSliverAggregateUrn;
    private GeniUrn createdSliverCreatorUrn;
    private Date createdSliverExpirationDate;

    @Test(hardDepends = {"lookupSliverInfoNoFilter"},
            groups = {"sliver_info"})
    public void createSliverInfo() throws JFedException, ParseException {
        if (!hasSliverInfoService) skip("No SLIVER_INFO service advertised in get_version");

        createdSliverUrn = GeniUrn.createGeniUrnFromDecodedParts("example.com", "sliver", sliceUrn.getResourceName());
        createdSliverAggregateUrn = GeniUrn.createGeniUrnFromDecodedParts("example.com", "authority", "cm");
        createdSliverCreatorUrn = getUser().getUserUrn();
        createdSliverExpirationDate = new Date(System.currentTimeMillis() + (60 * 60 * 1000L)); //1 hours from now

        AbstractFederationApi2.FederationApiReply<AbstractFederationApi.SliverInfo> reply =
                sa.createSliverInfo(getSAConnection(), sliceCredentials/*testUserCredentialList*/,
                        sliceUrn, createdSliverUrn, createdSliverAggregateUrn, createdSliverCreatorUrn, createdSliverExpirationDate,
                        null/*fields*/, null/*extraOptions*/);

        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        Map<String, Object> res = assertStringObjectMap(reply.getRawValue());
        assertNotNull(res);
        assertFalse(res.isEmpty());

        assertMapContainsString(res, "SLIVER_INFO_SLICE_URN", sliceUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_URN", createdSliverUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_CREATOR_URN", createdSliverCreatorUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_AGGREGATE_URN", createdSliverAggregateUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_CREATION");
        String recievedExpireDate = assertMapContainsString(res, "SLIVER_INFO_EXPIRATION");
        assertEqualDatesNoMs(createdSliverExpirationDate, RFC3339Util.rfc3339StringToDate(recievedExpireDate));
    }

    @Test(hardDepends = {"createSliverInfo"},
            groups = {"sliver_info"})
    public void lookupCreatedSliverInfo() throws JFedException, ParseException {
        if (!hasSliverInfoService) skip("No SLIVER_INFO service advertised in get_version");

        Map<String, String> match = new HashMap<>();
        List<String> filter = null;// new List<String>();

        AbstractFederationApi.FederationApiReply<FederationSliceAuthorityApi2.SliverInfoList>
                reply =
                sa.lookupSliverInfo(getSAConnection(), sliceCredentials/*testUserCredentialList*/, sliceUrn, match, filter, null);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        checkLookupCorrectness(reply);
        Map<String, Object> lookupRes = assertStringObjectMap(reply.getRawValue());
        assertNotNull(lookupRes);
        assertTrue(lookupRes.size() == 1);
        assertTrue(lookupRes.keySet().contains(createdSliverUrn.toString()), "lookup result does not contain key \"" + createdSliverUrn + "\"");

        Map<String, Object> res = assertStringObjectMap(lookupRes.get(createdSliverUrn.getValue()));
        assertNotNull(res);
        assertFalse(res.isEmpty());

        assertMapContainsString(res, "SLIVER_INFO_SLICE_URN", sliceUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_URN", createdSliverUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_CREATOR_URN", createdSliverCreatorUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_AGGREGATE_URN", createdSliverAggregateUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_CREATION");
        String recievedExpireDate = assertMapContainsString(res, "SLIVER_INFO_EXPIRATION");
        assertEqualDatesNoMs(createdSliverExpirationDate, RFC3339Util.rfc3339StringToDate(recievedExpireDate));
    }

    private Date updatedSliverExpirationDate;

    @Test(hardDepends = {"lookupCreatedSliverInfo"},
            groups = {"sliver_info"})
    public void updateSliverInfo() throws JFedException, ParseException {
        if (!hasSliverInfoService) skip("No SLIVER_INFO service advertised in get_version");

        updatedSliverExpirationDate =
                new Date(System.currentTimeMillis() + ((2 * 60 * 60 * 1000L) + (11 * 60 * 1000L))); //2 hours, 11 min from now

        Map<String, String> fields = new HashMap<>();
        fields.put("SLIVER_INFO_EXPIRATION", RFC3339Util.dateToRFC3339String(updatedSliverExpirationDate,
                true/*forceZuluHack*/, true/*discardSubSeconds*/, true/*useZinsteadOfZero*/));

        AbstractFederationApi2.FederationApiReply<String> reply =
                sa.updateSliverInfo(getSAConnection(), sliceCredentials/*testUserCredentialList*/,
                        createdSliverUrn.toString(),
                        fields, null/*extraOptions*/);

        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());
    }

    @Test(hardDepends = {"updateSliverInfo"},
            groups = {"sliver_info"})
    public void lookupUpdatedSliverInfo() throws JFedException, ParseException {
        if (!hasSliverInfoService) skip("No SLIVER_INFO service advertised in get_version");

        Map<String, String> match = new HashMap<>();
        List<String> filter = null;// new List<String>();

        AbstractFederationApi.FederationApiReply<AbstractFederationApi.SliverInfoList>
                reply =
                sa.lookupSliverInfo(getSAConnection(), sliceCredentials/*testUserCredentialList*/, sliceUrn, match, filter, null);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        checkLookupCorrectness(reply);
        Map<String, Object> lookupRes = assertStringObjectMap(reply.getRawValue());
        assertNotNull(lookupRes);
        assertTrue(lookupRes.size() == 1);
        assertTrue(lookupRes.keySet().contains(createdSliverUrn.toString()), "lookup result does not contain key \"" + createdSliverUrn + "\"");

        Map<String, Object> res = assertStringObjectMap(lookupRes.get(createdSliverUrn.getValue()));
        assertNotNull(res);
        assertFalse(res.isEmpty());

        assertMapContainsString(res, "SLIVER_INFO_SLICE_URN", sliceUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_URN", createdSliverUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_CREATOR_URN", createdSliverCreatorUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_AGGREGATE_URN", createdSliverAggregateUrn.toString());
        assertMapContainsString(res, "SLIVER_INFO_CREATION");
        String recievedExpireDate = assertMapContainsString(res, "SLIVER_INFO_EXPIRATION");
        assertEqualDatesNoMs(updatedSliverExpirationDate, RFC3339Util.rfc3339StringToDate(recievedExpireDate));
    }

    @Test(hardDepends = {"createSliverInfo"},
            softDepends = {"lookupUpdatedSliverInfo"},
            groups = {"sliver_info"})
    public void deleteSliverInfo() throws JFedException, ParseException {
        if (!hasSliverInfoService) skip("No SLIVER_INFO service advertised in get_version");

        AbstractFederationApi2.FederationApiReply<Boolean> reply =
                sa.deleteSliverInfo(getSAConnection(), sliceCredentials/*testUserCredentialList*/, createdSliverUrn.toString(), null/*extraOptions*/);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        checkCorrectnessXmlRpcResult(reply);
        //Boolean lookupRes = reply.getValue();
        //assertNotNull(lookupRes);
        //assertTrue(lookupRes);
    }

    @Test(softDepends = {"deleteSliverInfo"},
            groups = {"sliver_info"})
    public void lookupDeletedSliverInfo() throws JFedException {
        Map<String, String> match = new HashMap<>();
        List<String> filter = null;// new List<String>();

        AbstractFederationApi.FederationApiReply<AbstractFederationApi.SliverInfoList>
                reply =
                sa.lookupSliverInfo(getSAConnection(), sliceCredentials/*testUserCredentialList*/, sliceUrn, match, filter, null);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        checkLookupCorrectness(reply);
        Map<String, Object> lookupRes = assertStringObjectMap(reply.getRawValue());
        assertNotNull(lookupRes);
        assertTrue(lookupRes.size() == 0);

        note("This test just checks that there are no slivers in the slice anymore.");
    }

    ////////////////////////////////////////////////// PROJECT methods //////////////////////////////////////////////////

    private String projectName;
    private GeniUrn projectUrn;
    private Map<String, Object> projectCreationDetails;
    private boolean projectSuccessFullyCreated = false;
    private static final String initialProjectDescription = "test project used in automated testing";

    @Test(hardDepends = {"retrieveCredentialSomehow"}, softDepends = {"getVersion", "getTestUserInfo"}, groups = {
            "project"})
    public void createProject() throws JFedException, GeniUrn.GeniUrnParseException {
        if (!hasProjectService) skip("No PROJECT service advertised in get_version");

        String createdProjectName = "P" + System.currentTimeMillis();
        GeniUrn createdExpectedProjectUrn =
                GeniUrn.createGeniUrnFromEncodedParts(testedAuthority.getServerToConnect().getUrnTld(), "project", createdProjectName);

        Map<String, String> fields = new HashMap<>();
        fields.put("PROJECT_DESCRIPTION", initialProjectDescription);
        fields.put("PROJECT_EXPIRATION", RFC3339Util.dateToRFC3339String(new Date(System.currentTimeMillis() + 3 * 60 * 60 * 1000), true, true, true));

        if (getVersionResult == null)
            warn("get_version result is unknown to this test method. Cannot add any required supplemental fields.");
        else {
            if (getVersionResult.getFields() == null)
                warn("getVersionResult fieldInfos map is null. Bug in jFed?");
            else {
                note("All Required supplemental fields: " + getVersionResult.getFields().keySet());
            }
            if (getVersionResult.getFieldsForObject("PROJECT") == null)
                warn("getVersionResult fieldInfos map is null when called with getFieldsForObject(\"PROJECT\"). Bug in jFed?");
            else {
                note("Required supplemental fields for PROJECT are: " + getVersionResult.getFieldsForObject("PROJECT").keySet());
            }
        }
        if (getVersionResult != null && getVersionResult.getFieldsForObject("PROJECT") != null) {
            if (getVersionResult.getFieldsForObject("PROJECT").containsKey("_GENI_PROJECT_EMAIL")) {
                note("get_version contains supplementary field _GENI_PROJECT_EMAIL. Will fill in user email.");
                if (testUserInfo != null && testUserInfo.containsKey("MEMBER_EMAIL"))
                    fields.put("_GENI_PROJECT_EMAIL", testUserInfo.get("MEMBER_EMAIL") + "");
                else {
                    warn("create_project requires the field _GENI_PROJECT_EMAIL, but the current users EMAIL is unknown. (the value 'dummy@example.com' will be used, but will likely cause problems)");
                    fields.put("_GENI_PROJECT_EMAIL", "dummy@example.com");
                }
            }
            if (getVersionResult.getFieldsForObject("PROJECT").containsKey("_GENI_PROJECT_OWNER")) { //type UID
                note("get_version contains supplementary field _GENI_PROJECT_OWNER. Will fill in user UID");
                if (testUserInfo != null && testUserInfo.containsKey("MEMBER_UID"))
                    fields.put("_GENI_PROJECT_OWNER", testUserInfo.get("MEMBER_UID") + "");
                else {
                    skip("create_project requires the field _GENI_PROJECT_OWNER, but the current users UID is unknown. (getTestUserInfo probably failed)");
                    warn("create_project requires the field _GENI_PROJECT_OWNER, but the current users UID is unknown. (the value 'unknown' will be used, but will likely cause problems)");
                    fields.put("_GENI_PROJECT_OWNER", "unknown");
                }

            }
        }
        addRequiredSupplementaryFieldsToCreate(fields, "PROJECT");

        AbstractFederationApi2.FederationApiReply<AbstractFederationApi.ProjectInfo> reply =
                sa.createProject(getSAConnection(), testUserCredentialList, createdProjectName, fields, null);

        if (reply.getOutput() != null && !reply.getGeniResponseCode().isSuccess() &&
                (reply.getOutput().contains("not supported") || reply.getOutput().contains("is not implemented")))
            skip("This server does not support this method");

        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        projectCreationDetails = assertStringObjectMap(reply.getRawValue());
        assertNotNull(projectCreationDetails);
        note("project details contains the following fields: " + projectCreationDetails.keySet());

        setErrorsNotFatal();
        String createdFoundProjectUrn = assertMapContainsString(projectCreationDetails, "PROJECT_URN");
        if (!Objects.equals(createdExpectedProjectUrn.getValue(), createdFoundProjectUrn))
            warn("Expected PROJECT URN (\"" + createdExpectedProjectUrn + "\") differs from found created project URN (\"" + createdFoundProjectUrn + "\")");
        setErrorsFatal();

        //TODO: try to set a short expiration time here or in update

        if (currentTestResult.getState() != ApiTestResult.TestResultState.FAILED) {
            projectSuccessFullyCreated = true;
            this.projectName = createdProjectName;
            this.projectUrn = new GeniUrn(createdFoundProjectUrn);
        } else {
            projectSuccessFullyCreated = false;
            this.projectName = null;
            this.projectUrn = null;
        }
    }

    /** */
    @Test(softDepends = {"getVersion", "getTestUserInfo",
                         "createProject"}, description = "Make sure a project is found that can be used for the next tests. " +
            "This will use the created project, and if that failed, it will search for one.", groups = {"project",
                                                                                                        "slice",
                                                                                                        "slice_member",
                                                                                                        "sliver_info"})
    public void findProjectToUseInTests() throws JFedException, GeniUrn.GeniUrnParseException {
        if (!hasProjectService) skip("No PROJECT service advertised in get_version");

        if (projectSuccessFullyCreated && projectName != null && projectUrn != null && projectCreationDetails != null) {
            note("Will use the project created in \"createProject\": " + projectUrn);
            return;
        } else
            note("Will not use the created project, because some information is missing. (Or because no project was created.)");

        Map<String, String> match = new HashMap<>();
        List<String> filter = null;//new List<String>();

//        if (getVersionResult.getFieldsForObject("PROJECT").containsKey("_GENI_PROJECT_OWNER")) {
//            if (testUserInfo == null || !testUserInfo.containsKey("MEMBER_UID"))
//                skip("No MEMBER info could be retrieved (getTestUserInfo probably failed), so it cannot be used here: skipping test.");
//            match.put("_GENI_PROJECT_OWNER", (String) testUserInfo.get("MEMBER_UID"));
//        }
//
//        AbstractFederationApi2.FederationApiReply<AbstractFederationApi2.LookupResult> reply =
//                sa.lookupProject(getSAConnection(), testUserCredentialList, match, filter, null);
//        checkCorrectnessXmlRpcResult(reply);


        GeniUrn testUserUrn = GeniUrn.parse(user.getUserUrnString());
        AbstractFederationApi2.FederationApiReply<List<FederationSliceAuthorityApi2.UrnRoleTuple>> reply =
                sa.lookupForMember(getSAConnection(), "PROJECT", testUserUrn, testUserCredentialList, null);
        checkCorrectnessXmlRpcResult(reply);

        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        List<FederationSliceAuthorityApi2.UrnRoleTuple> res = reply.getValue();
        assertFalse(res.isEmpty());

        GeniUrn firstUrn = res.get(0).getUrn();
        this.projectUrn = firstUrn;
        this.projectName = projectUrn.getEncodedResourceName();
        this.projectCreationDetails = null; //not needed elsewhere, only URN needed
    }


    private void checkProjectLookupResult(AbstractFederationApi2.FederationApiReply<AbstractFederationApi.ProjectInfoList> reply, List<String> expectedFieldNames, List<String> excludedFieldNames) {
        Map<String, Object> lookupRes = assertStringObjectMap(reply.getRawValue());
        checkCorrectnessXmlRpcResult(reply);
        checkLookupCorrectness(reply);
        assertNotNull(lookupRes);

        assertTrue(lookupRes.size() > 0, "Lookup result is empty for project urn: " + projectUrn.getValue());

        errorNonFatalIfNot(lookupRes.size() == 1, "lookup must return one project, but it returned " + lookupRes.size());

        Map<String, Object> rawUser = assertStringObjectMap(lookupRes.get(projectUrn.getValue()));
        errorNonFatalIfNot(rawUser != null, "lookup must return requested project (" + projectUrn.getValue() + ") but returned null.");

        String rawUrn = (String) rawUser.get("PROJECT_URN"); //check that if this project has a project urn, it matches.
        if (rawUrn != null && !Objects.equals(projectUrn.getValue(), rawUrn))
            errorNonFatal("lookup must return requested project (" + projectUrn.getValue() + "), and it did, however its returned PROJECT_URN entry is not matching: " + rawUrn);

        String rawName =
                (String) rawUser.get("PROJECT_NAME"); //check that if this project has a project name, it matches.
        if (rawName != null && !Objects.equals(projectName, rawName))
            errorNonFatal("lookup must return requested project (urn=" + projectUrn.getValue() + " name=" + projectName + "), and it did, however its returned PROJECT_Name entry is not matching: " + rawName);

        for (Object projectInfo : lookupRes.values()) {
            Map<String, Object> rawProjectInfo = assertStringObjectMap(projectInfo);
            for (String fieldName : expectedFieldNames) {
                if (!rawProjectInfo.containsKey(fieldName))
                    errorNonFatal("returned result does not contain field: \"" + fieldName + "\"");
            }
            for (String fieldName : excludedFieldNames) {
                if (rawProjectInfo.containsKey(fieldName))
                    errorNonFatal("returned result contains unexpected field: \"" + fieldName + "\"");
            }
        }
    }

    @Test(hardDepends = {"findProjectToUseInTests"}, groups = {"project"})
    public void lookupProjectsByUrnNoFilter() throws JFedException {
        if (!hasProjectService) skip("No PROJECT service advertised in get_version");

        Map<String, String> match = new HashMap<>();

        match.put("PROJECT_URN", projectUrn.getValue());

        note("looking up project by PROJECT_URN: " + projectUrn.getValue());
        AbstractFederationApi2.FederationApiReply<AbstractFederationApi.ProjectInfoList> reply =
                sa.lookupProject(getSAConnection(), testUserCredentialList, match, null, null);
        checkCorrectnessXmlRpcResult(reply);

        if (reply.getOutput() != null && !reply.getGeniResponseCode().isSuccess() &&
                (reply.getOutput().contains("not supported") || reply.getOutput().contains("is not implemented")))
            skip("This server does not support this method");

        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        List<String> expectedFieldNames = new ArrayList<>();
        List<String> excludedFieldNames = new ArrayList<>();
        for (AbstractFederationApi2.GetVersionResult.FieldInfo fi : projectDefaultFields)
            expectedFieldNames.add(fi.getName());
        note("No filter provided, so expecting all fields to be returned: " + expectedFieldNames);
        checkProjectLookupResult(reply, expectedFieldNames, excludedFieldNames);

        final Map<String, Object> rawHt = assertStringObjectMap(reply.getRawValue());
        final Map<String, Object> projectHt = assertStringObjectMap(rawHt.get(projectUrn.getValue()));
        String desc = assertMapContainsNonemptyString(projectHt, "PROJECT_DESCRIPTION");
        if (projectSuccessFullyCreated)
            assertEquals(desc, initialProjectDescription);
    }

//    @Test(hardDepends = {"findProjectToUseInTests"}, groups = {"project"})
//    public void lookupProjectsByNameNoFilter() throws JFedException {
//        if (!hasProjectService) skip("No PROJECT service advertised in get_version");
//
//        Map<String, String> match = new Map<String, String>();
//        List<String> filter = null;//new List<String>();
//
//        match.put("PROJECT_NAME", projectName);
//
//        note("looking up project by PROJECT_URN: " + projectUrn.getValue());
//        AbstractFederationApi2.FederationApiReply<AbstractFederationApi2.LookupResult> reply =
//                sa.lookupProject(getSAConnection(), testUserCredentialList, match, filter, null);
//        checkCorrectnessXmlRpcResult(reply);
//
//        if (reply.getOutput() != null && !reply.getGeniResponseCode().isSuccess() &&
//                (reply.getOutput().contains("not supported") || reply.getOutput().contains("is not implemented")))
//            skip("This server does not support this method");
//
//        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());
//
//        List<String> expectedFieldNames = new ArrayList<String>();
//        List<String> excludedFieldNames = new ArrayList<String>();
//        for (AbstractFederationApi2.GetVersionResult.FieldInfo fi : projectDefaultFields)
//            expectedFieldNames.add(fi.getName());
//        note("No filter provided, so expecting all fields to be returned: " + expectedFieldNames);
//        checkProjectLookupResult(reply, expectedFieldNames, excludedFieldNames);
//
//        String desc = (String) reply.getValue().get(projectUrn).get("PROJECT_DESCRIPTION");
//        assertNotNull(desc);
//        if (projectSuccessFullyCreated)
//            assertEquals(desc, initialProjectDescription);
//    }

    private static final String updatedProjectDescription =
            "test project used in automated testing with updated description";

    @Test(hardDepends = {"createProject"}, softDepends = {"lookupProjectsByUrnNoFilter"
                                                          /*,"lookupProjectsByNameNoFilter"*/}, groups = {"project"})
    public void updateProject() throws JFedException {
        if (!hasProjectService) skip("No PROJECT service advertised in get_version");
        if (!projectSuccessFullyCreated) skip("This test will not update a project it did not create itself.");

        Map<String, String> fields = new HashMap<>();
        fields.put("PROJECT_DESCRIPTION", updatedProjectDescription);

        AbstractFederationApi2.FederationApiReply<String> reply =
                sa.updateProject(getSAConnection(), testUserCredentialList, projectUrn, fields, null);
        checkCorrectnessXmlRpcResult(reply);

        if (reply.getOutput() != null && !reply.getGeniResponseCode().isSuccess() &&
                (reply.getOutput().contains("not supported") || reply.getOutput().contains("is not implemented")))
            skip("This server does not support this method");

        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());
    }

    @Test(hardDepends = {"updateProject"}, groups = {"project"})
    public void lookupProjectsNoFilterAfterUpdate() throws JFedException {
        if (!hasProjectService) skip("No PROJECT service advertised in get_version");

        Map<String, String> match = new HashMap<>();

        match.put("PROJECT_URN", projectUrn.getValue());

        note("looking up project by PROJECT_URN: " + projectUrn.getValue());
        AbstractFederationApi2.FederationApiReply<AbstractFederationApi.ProjectInfoList> reply =
                sa.lookupProject(getSAConnection(), testUserCredentialList, match, null, null);
        checkCorrectnessXmlRpcResult(reply);

        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        List<String> expectedFieldNames = new ArrayList<>();
        List<String> excludedFieldNames = new ArrayList<>();
        for (AbstractFederationApi2.GetVersionResult.FieldInfo fi : projectDefaultFields)
            expectedFieldNames.add(fi.getName());
        note("No filter provided, so expecting all fields to be returned: " + expectedFieldNames);
        checkProjectLookupResult(reply, expectedFieldNames, excludedFieldNames);

        final Map<String, Object> rawHt = assertStringObjectMap(reply.getRawValue());
        final Map<String, Object> projectHt = assertStringObjectMap(rawHt.get(projectUrn.getValue()));
        String desc = assertMapContainsNonemptyString(projectHt, "PROJECT_DESCRIPTION");
        if (projectSuccessFullyCreated)
            assertEquals(desc, updatedProjectDescription);
    }

    ////////////////////////////////////////////////// PROJECT_MEMBER methods //////////////////////////////////////////////////

    private String lookupProjectMembersRole;

    @Test(hardDepends = {"retrieveCredentialSomehow", "findProjectToUseInTests"}, softDepends = {
            "lookupProjectsNoFilterAfterUpdate"}, groups = {"project_member"})
    public void lookupProjectMembers() throws JFedException {
        if (!hasProjectMemberService) skip("No PROJECT_MEMBER service advertised in get_version");

        AbstractFederationApi2.FederationApiReply<List<FederationSliceAuthorityApi2.UrnRoleTuple>> reply =
                sa.lookupMembers(getSAConnection(), "PROJECT", projectUrn, testUserCredentialList, null);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        List<FederationSliceAuthorityApi2.UrnRoleTuple> memberTuples = reply.getValue();
        assertNotNull(memberTuples);
        assertNotEmpty(memberTuples);
        if (projectSuccessFullyCreated)
            assertTrue(memberTuples.size() == 1, "Expected exactly 1 member in project created by test, but got: " + memberTuples);

        FederationSliceAuthorityApi2.UrnRoleTuple testUserMemberTuple = null;// = memberTuples.get(0);
        List<String> memberUrns = new ArrayList<>();
        for (FederationSliceAuthorityApi2.UrnRoleTuple curMemberTuple : memberTuples) {
            memberUrns.add(curMemberTuple.getUrn().toString());
            if (Objects.equals(curMemberTuple.getUrn().toString(), user.getUserUrnString()))
                testUserMemberTuple = curMemberTuple;
        }

        assertTrue(memberUrns.contains(user.getUserUrnString()),
                "Expected project member to be " + user.getUserUrnString() + " but project members are: " + memberUrns);
        assertNotNull(testUserMemberTuple, "member not found in project. all member urns: " + memberUrns);

        note("test user role in project is: " + testUserMemberTuple.role);
        assertNotNull(testUserMemberTuple.role, "member role is null in project");
        lookupProjectMembersRole = testUserMemberTuple.role;
    }

    @Test(hardDepends = {"findProjectToUseInTests"}, softDepends = {"lookupProjectMembers"}, groups = {
            "project_member"})
    public void lookupProjectsForMember() throws JFedException {
        if (!hasProjectMemberService) skip("No PROJECT_MEMBER service advertised in get_version");

        GeniUrn memberUrn = GeniUrn.parse(user.getUserUrnString());
        assertNotNull(memberUrn);

        AbstractFederationApi2.FederationApiReply<List<FederationSliceAuthorityApi2.UrnRoleTuple>> reply =
                sa.lookupForMember(getSAConnection(), "PROJECT", memberUrn, testUserCredentialList, null);
        assertTrue(reply.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply.getGeniResponseCode());

        List<FederationSliceAuthorityApi2.UrnRoleTuple> value = reply.getValue();

        assertNotNull(value);
        assertNotEmpty(value);
        assertTrue(value.size() >= 1, "should have been at least 1 project for " + memberUrn);
        note("Project urns for member " + memberUrn + ": " + value);

        String role = null;
        for (FederationSliceAuthorityApi2.UrnRoleTuple tuple : value) {
            if (Objects.equals(tuple.getUrn(), projectUrn))
                role = tuple.getRole();
            assertEquals(tuple.getUrn().getEncodedResourceType(), "project", "The returned URN is not a project urn but is of type: \"" + tuple.getUrn().getEncodedResourceType() + "\"");
        }

        assertNotNull(role, "project " + projectUrn + " was not listed as project for member " + memberUrn + ". All: " + value);
        if (lookupProjectMembersRole != null)
            assertEquals(role, lookupProjectMembersRole, "A different role was returned for " + memberUrn + " in " + projectUrn + " for the lookup_projects_for_members and lookup_project_members calls");
    }

    @Test(hardDepends = {"getVersion", "createProject", "lookupProjectMembers"}, softDepends = {
            "lookupProjectsForMember"}, groups = {"project_member"})
    public void modifyProjectMembership() throws JFedException {
        if (!projectSuccessFullyCreated) skip("This test will not update a project it did not create itself.");
        assertNotNull(allRoles, "get_version test did not set allRoles");
        assertNotNull(lookupProjectMembersRole, "lookupProjectMembersRole not set in lookupProjectMembers");

        if (!hasProjectMemberService) skip("No PROJECT_MEMBER service advertised in get_version");

        List<FederationSliceAuthorityApi2.UrnRoleTuple> membersToAdd =
                new ArrayList<>();
        List<GeniUrn> membersToRemove = new ArrayList<>();
        List<FederationSliceAuthorityApi2.UrnRoleTuple> membersToChange =
                new ArrayList<>();

        if (allRoles.size() <= 1) {
            skip("server does not have multiple roles: cannot test modify_project_membership");
        }

        String newRole = allRoles.get(0);
        String oldRole = lookupProjectMembersRole;

        if (allRoles.contains("USER") && !Objects.equals(oldRole, "USER")) newRole = "USER";
        if (allRoles.contains("MEMBER") && !Objects.equals(oldRole, "MEMBER")) newRole = "MEMBER";
        if (getTestConfig().contains("modify_project_membership_test_new_role")) {
            String nr = getTestConfig().getProperty("modify_project_membership_test_new_role");
            if (allRoles.contains(nr)) {
                note("Changing role to role defined in modify_project_membership_test_new_role: \"" + nr + "\"");
                newRole = nr;
            } else {
                fatalError("modify_project_membership_test_new_role is set to \"" + nr + "\", but this rol was NOT advertised in get_version. Advertised roles are: " + allRoles);
            }
        }

        for (int i = 1; Objects.equals(newRole, oldRole) && i < allRoles.size(); i++)
            newRole = allRoles.get(i);
        assertNotEquals(newRole, oldRole, "roles defined in get_version do not differ: oldRole=" + oldRole + " allRoles=" + allRoles);

        membersToChange.add(new FederationSliceAuthorityApi2.UrnRoleTuple("PROJECT", user.getUserUrn(), newRole));
        note("Will try to change role of member \"" + user.getUserUrnString() + "\" in project \"" + projectUrn + "\" from \"" + oldRole + "\" to \"" + newRole + "\"");

        AbstractFederationApi2.FederationApiReply<String> reply1 =
                sa.modifyMembership(getSAConnection(), "PROJECT", projectUrn, membersToAdd, membersToRemove, membersToChange, testUserCredentialList, null);
        checkCorrectnessXmlRpcResult(reply1);

        assertTrue(reply1.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply1.getGeniResponseCode());


        //lookup and check if changed correctly
        AbstractFederationApi2.FederationApiReply<List<FederationSliceAuthorityApi2.UrnRoleTuple>> reply2 =
                sa.lookupMembers(getSAConnection(), "PROJECT", projectUrn, testUserCredentialList, null);
        assertTrue(reply2.getGeniResponseCode().isSuccess(), "The tested call did not return code 0 (success), instead it returned " + reply2.getGeniResponseCode());

        List<FederationSliceAuthorityApi2.UrnRoleTuple> memberTuples = reply2.getValue();
        assertNotNull(memberTuples);
        assertNotEmpty(memberTuples);
        assertTrue(memberTuples.size() == 1, "Expected exactly 1 member in project: " + memberTuples);

        FederationSliceAuthorityApi2.UrnRoleTuple memberTuple = memberTuples.get(0);
        assertEquals(memberTuple.urn.getValue(), user.getUserUrnString(), "Expected project member to be " + user.getUserUrnString() + " but it was " + memberTuple.urn.getValue());
        note("test user role in project is: " + memberTuple.role);
        assertNotNull(memberTuple.role, "member role is null in project");
        assertEquals(memberTuple.role, newRole, "member role differs from newly assigned role");
        lookupProjectMembersRole = memberTuple.role;
    }

}
