// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
package groovy.org.apache.cloudstack.ldap

import org.apache.cloudstack.api.command.LdapAddConfigurationCmd
import org.apache.cloudstack.api.command.LdapCreateAccountCmd
import org.apache.cloudstack.api.command.LdapDeleteConfigurationCmd
import org.apache.cloudstack.api.command.LdapImportUsersCmd
import org.apache.cloudstack.api.command.LdapListUsersCmd
import org.apache.cloudstack.api.command.LdapUserSearchCmd

import javax.naming.NamingException
import javax.naming.ldap.InitialLdapContext

import org.apache.cloudstack.api.command.LdapListConfigurationCmd
import org.apache.cloudstack.ldap.*
import org.apache.cloudstack.ldap.dao.LdapConfigurationDaoImpl

import com.cloud.exception.InvalidParameterValueException
import com.cloud.utils.Pair

class LdapManagerImplSpec extends spock.lang.Specification {
    def "Test failing of getUser due to bind issue"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapContextFactory.createBindContext() >> { throw new NamingException() }
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "We search for a user but there is a bind issue"
		ldapManager.getUser("rmurphy")
		then: "an exception is thrown"
		thrown NamingException
    }

    def "Test failing of getUsers due to bind issue"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapContextFactory.createBindContext() >> { throw new NamingException() }
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "We search for a group of users but there is a bind issue"
		ldapManager.getUsers()
		then: "An exception is thrown"
		thrown NoLdapUserMatchingQueryException
    }

    def "Test failing of searchUsers due to a failure to bind"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapContextFactory.createBindContext() >> { throw new NamingException() }
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "We search for users"
		ldapManager.searchUsers("rmurphy")
		then: "An exception is thrown"
		thrown NoLdapUserMatchingQueryException
    }

    def "Test LdapConfigurationResponse generation"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "A ldap configuration response is generated"
		def result = ldapManager.createLdapConfigurationResponse(new LdapConfigurationVO("localhost", 389))
		then: "the result of the response should match the given LdapConfigurationVO"
		result.hostname == "localhost"
		result.port == 389
    }

    def "Test LdapUserResponse generation"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "A ldap user response is generated"
		def result = ldapManager.createLdapUserResponse(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org",
		"engineering"))
		then: "The result of the response should match the given ldap user"
		result.username == "rmurphy"
		result.email == "rmurphy@test.com"
		result.firstname == "Ryan"
		result.lastname == "Murphy"
		result.principal == "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org"
	result.domain == "engineering"
    }

    def "Test success getUsers"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapContextFactory.createBindContext() >> null
		List<LdapUser> users = new ArrayList<>();
		users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null))
		ldapUserManager.getUsers(_) >> users;
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "We search for a group of users"
		def result = ldapManager.getUsers()
		then: "A list greater than 0 is returned"
		result.size() > 0;
    }

    def "Test success of getUser"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapContextFactory.createBindContext() >> null
		ldapUserManager.getUser(_, _) >> new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null)
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "We search for a user"
		def result = ldapManager.getUser("rmurphy")
		then: "The user is returned"
		result.username == "rmurphy"
		result.email == "rmurphy@test.com"
		result.firstname == "Ryan"
		result.lastname == "Murphy"
		result.principal == "cn=rmurphy,dc=cloudstack,dc=org"
    }

    def "Test successful closing of context"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "The context is closed"
		def context = Mock(InitialLdapContext)
		ldapManager.closeContext(context)
		then: "The context is null"
		context.defaultInitCtx == null
    }

    def "Test successful failed result from canAuthenticate due to bad password"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
        def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
        def ldapContextFactory = Mock(LdapContextFactory)
        ldapContextFactory.createUserContext(_, _) >> { throw new NamingException() }
        def ldapUserManager = Mock(LdapUserManager)
        def ldapManager = Spy(LdapManagerImpl, constructorArgs: [ldapConfigurationDao, ldapContextFactory, ldapUserManager])
	ldapManager.getUser(_) >> { new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null) }
		when: "The user attempts to authenticate with a bad password"
        def result = ldapManager.canAuthenticate("rmurphy", "password")
		then: "The authentication fails"
		result == false
    }

    def "Test successful failed result from canAuthenticate due to user not found"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		def ldapManager = Spy(LdapManagerImpl, constructorArgs: [ldapConfigurationDao, ldapContextFactory, ldapUserManager])
		ldapManager.getUser(_) >> { throw new NamingException() }
		when: "The user attempts to authenticate and the user is not found"
		def result = ldapManager.canAuthenticate("rmurphy", "password")
		then: "the authentication fails"
		result == false
    }

    def "Test successful failed result from deleteConfiguration due to configuration not existing"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapConfigurationDao.findByHostname(_) >> null
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "A ldap configuration that doesn't exist is deleted"
		ldapManager.deleteConfiguration("localhost")
		then: "A exception is thrown"
		thrown InvalidParameterValueException
    }

    def "Test successful failing to close of context"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
        def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
        def ldapContextFactory = Mock(LdapContextFactory)
        def ldapUserManager = Mock(LdapUserManager)
        def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "The context is closed"
        def context = Mock(InitialLdapContext)
        context.close() >> { throw new NamingException() }
        ldapManager.closeContext(context)
		then: "An exception is thrown"
		context.defaultInitCtx == null
    }

    def "Test successful result from canAuthenticate"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		ldapContextFactory.createUserContext(_, _) >> null
		def ldapUserManager = Mock(LdapUserManager)
		def ldapManager = Spy(LdapManagerImpl, constructorArgs: [ldapConfigurationDao, ldapContextFactory, ldapUserManager])
		ldapManager.getUser(_) >> { new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null) }
		when: "A user authenticates"
		def result = ldapManager.canAuthenticate("rmurphy", "password")
		then: "The result is true"
		result == true
    }

    def "Test successful result from deleteConfiguration"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapConfigurationDao.findByHostname(_) >> {
		    def configuration = new LdapConfigurationVO("localhost", 389)
		    configuration.setId(0);
		    return configuration;
		}
		ldapConfigurationDao.remove(_) >> null
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "A ldap configuration is deleted"
		def result = ldapManager.deleteConfiguration("localhost")
		then: "The deleted configuration is returned"
		result.hostname == "localhost"
		result.port == 389
    }

    def "Test successful result from searchUsers"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapContextFactory.createBindContext() >> null;

		List<LdapUser> users = new ArrayList<LdapUser>();
		users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,ou=engineering,dc=cloudstack,dc=org", "engineering"))
		ldapUserManager.getUsers(_, _) >> users;

		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "We search for users"
		def result = ldapManager.searchUsers("rmurphy");
		then: "A list of atleast 1 is returned"
		result.size() > 0;
    }

    def "Test successfully addConfiguration"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapContextFactory.createBindContext(_) >> null
		ldapConfigurationDao.persist(_) >> null
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "A ldap configuration is added"
		def result = ldapManager.addConfiguration("localhost", 389)
		then: "the resulting object contain the given hostname and port"
		result.hostname == "localhost"
		result.port == 389
    }

    def "Test that addConfiguration fails when a binding fails"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapContextFactory.createBindContext(_) >> { throw new NamingException() }
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "A configuration is added that can not be binded"
		ldapManager.addConfiguration("localhost", 389)
		then: "An exception is thrown"
		thrown InvalidParameterValueException
    }

    def "Test that addConfiguration fails when a duplicate configuration exists"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		ldapConfigurationDao.findByHostname(_) >> new LdapConfigurationVO("localhost", 389)
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "a configuration that already exists is added"
		ldapManager.addConfiguration("localhost", 389)
		then: "An exception is thrown"
		thrown InvalidParameterValueException
    }

    def supportedLdapCommands() {
	List<Class<?>> cmdList = new ArrayList<Class<?>>();
	cmdList.add(LdapUserSearchCmd.class);
	cmdList.add(LdapListUsersCmd.class);
	cmdList.add(LdapAddConfigurationCmd.class);
	cmdList.add(LdapDeleteConfigurationCmd.class);
	cmdList.add(LdapListConfigurationCmd.class);
	cmdList.add(LdapCreateAccountCmd.class);
	cmdList.add(LdapImportUsersCmd.class);
	return cmdList
    }

    def "Test that getCommands isn't empty"() {
		given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
	final List<Class<?>> cmdList = supportedLdapCommands()
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "Get commands is called"
		def result = ldapManager.getCommands()
		then: "it must return all the commands"
		result.size() > 0
	result == cmdList
    }

    def "Testing of listConfigurations"() {
	given:  "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
        def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
        def ldapContextFactory = Mock(LdapContextFactory)
        def ldapUserManager = Mock(LdapUserManager)
        List<LdapConfigurationVO> ldapConfigurationList = new ArrayList()
        ldapConfigurationList.add(new LdapConfigurationVO("localhost", 389))
        Pair<List<LdapConfigurationVO>, Integer> configurations = new Pair<List<LdapConfigurationVO>, Integer>();
        configurations.set(ldapConfigurationList, ldapConfigurationList.size())
        ldapConfigurationDao.searchConfigurations(_, _) >> configurations
        def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
	when: "A request for configurations is made"
        def result = ldapManager.listConfigurations(new LdapListConfigurationCmd())
		then: "Then atleast 1 ldap configuration is returned"
		result.second() > 0
    }

	def "Testing of isLdapEnabled"() {
		given:  "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
		def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
		def ldapContextFactory = Mock(LdapContextFactory)
		def ldapUserManager = Mock(LdapUserManager)
		List<LdapConfigurationVO> ldapConfigurationList = new ArrayList()
		ldapConfigurationList.add(new LdapConfigurationVO("localhost", 389))
		Pair<List<LdapConfigurationVO>, Integer> configurations = new Pair<List<LdapConfigurationVO>, Integer>();
		configurations.set(ldapConfigurationList, ldapConfigurationList.size())
		ldapConfigurationDao.searchConfigurations(_, _) >> configurations
		def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
		when: "A request to find out is ldap enabled"
		def result = ldapManager.isLdapEnabled();
		then: "true is returned because a configuration was found"
		result == true;
	}

    def "Test success getUsersInGroup"() {
	given: "We have an LdapConfigurationDao, LdapContextFactory, LdapUserManager and LdapManager"
	def ldapConfigurationDao = Mock(LdapConfigurationDaoImpl)
	def ldapContextFactory = Mock(LdapContextFactory)
	def ldapUserManager = Mock(LdapUserManager)
	ldapContextFactory.createBindContext() >> null
	List<LdapUser> users = new ArrayList<>();
	users.add(new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", "engineering"))
	ldapUserManager.getUsersInGroup("engineering", _) >> users;
	def ldapManager = new LdapManagerImpl(ldapConfigurationDao, ldapContextFactory, ldapUserManager)
	when: "We search for a group of users"
	def result = ldapManager.getUsersInGroup("engineering")
	then: "A list greater of size one is returned"
	result.size() == 1;
    }
}
