/*
 *   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 com.example.dao;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifs;
import org.apache.directory.server.core.annotations.ContextEntry;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreateIndex;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.directory.shared.ldap.entry.Entry;
import org.apache.directory.shared.ldap.name.DN;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import com.example.domain.InetOrgPerson;

@RunWith(FrameworkRunner.class)
@CreateDS( 
    name = "InetOrgPersonDaoTestDS",
    partitions =
    {
        @CreatePartition(
            name = "example",
            suffix = "dc=example,dc=com",
            contextEntry = @ContextEntry( 
                entryLdif =
                    "dn: dc=example,dc=com\n" +
                    "dc: example\n" +
                    "objectClass: top\n" +
                    "objectClass: domain\n\n" ),
            indexes = 
            {
                @CreateIndex( attribute = "objectClass" ),
                @CreateIndex( attribute = "dc" ),
                @CreateIndex( attribute = "ou" )
            } )
    } )
@CreateLdapServer ( 
    transports = 
    {
        @CreateTransport( protocol = "LDAP", port=12345 )
    })
@ApplyLdifs({
    "dn: ou=users,dc=example,dc=com",
    "objectClass: organizationalUnit",
    "objectClass: top",
    "ou: users",

    // 1st user
    "dn: uid=test1,ou=users,dc=example,dc=com",
    "objectClass: inetOrgPerson",
    "objectClass: organizationalPerson",
    "objectClass: person",
    "objectClass: top",
    "uid: test1",
    "cn: test1.cn",
    "sn: test1.sn",
    "givenName: test1.givenName",
    "telephoneNumber: 12345",
    "telephoneNumber: 23456",
    "userPassword:: dGVzdDEuc2VjcmV0",
    
    // 2nd user
    "dn: uid=test2,ou=users,dc=example,dc=com",
    "objectClass: inetOrgPerson",
    "objectClass: organizationalPerson",
    "objectClass: person",
    "objectClass: top",
    "uid: test2",
    "cn: test2.cn",
    "sn: test2.sn",
    "description: test2 description"
    // no givenName
    // no telephoneNumber
    // no userPassword

    })
public class InetOrgPersonDaoTest extends AbstractLdapTestUnit
{
    private InetOrgPersonDao dao;


    @Before
    public void setup()
    {
        dao = new InetOrgPersonDao();
    }


    @Test
    public void testGetById_Ok() throws Exception
    {
        InetOrgPerson user1 = dao.getById( "test1" );
        assertNotNull( user1 );
        assertEquals( "test1", user1.getUid() );
        assertEquals( "test1.cn", user1.getFullName() );
        assertEquals( "test1.sn", user1.getLastName() );
        assertEquals( "test1.givenName", user1.getFirstName() );
        assertNotNull( user1.getTelephoneNumbers() );
        assertEquals( 2, user1.getTelephoneNumbers().size() );
        assertTrue( user1.getTelephoneNumbers().contains( "12345" ) );
        assertTrue( user1.getTelephoneNumbers().contains( "23456" ) );
        assertEquals( "test1.secret", new String( user1.getPassword(), "UTF-8" ) );

        InetOrgPerson user2 = dao.getById( "test2" );
        assertNotNull( user2 );
        assertEquals( "test2", user2.getUid() );
        assertEquals( "test2.cn", user2.getFullName() );
        assertEquals( "test2.sn", user2.getLastName() );
        assertNull( user2.getFirstName() );
        assertNotNull( user2.getTelephoneNumbers() );
        assertEquals( 0, user2.getTelephoneNumbers().size() );
        assertNull( user2.getPassword() );
    }


    @Test
    public void testGetById_NonExisting()
    {
        InetOrgPerson person = dao.getById( "test3" );
        assertNull( person );
    }


    @Test
    public void testGetAll()
    {
        Collection<InetOrgPerson> all = dao.getAll();
        assertNotNull( all );
        assertEquals( 2, all.size() );
    }


    @Test
    public void testAdd() throws Exception
    {
        InetOrgPerson user = new InetOrgPerson();
        user.setUid( "test99" );
        user.setFullName( "test99.cn" );
        user.setFirstName( "test99.givenName" );
        user.setLastName( "test99.sn" );
        user.setPassword( "secret".getBytes( "UTF-8" ) );
        Set<String> telephoneNumbers = new HashSet<String>();
        telephoneNumbers.add( "11111" );
        telephoneNumbers.add( "22222" );
        user.setTelephoneNumbers( telephoneNumbers );
        dao.create( user );

        DN dn = new DN( "uid=test99,ou=users,dc=example,dc=com" );
        assertTrue( service.getAdminSession().exists( dn ) );
        Entry entry = service.getAdminSession().lookup( dn );
        assertNotNull( entry );

        assertAttribute( entry, "objectClass", "inetOrgPerson", "organizationalPerson", "person", "top" );
        assertAttribute( entry, "uid", "test99" );
        assertAttribute( entry, "cn", "test99.cn" );
        assertAttribute( entry, "sn", "test99.sn" );
        assertAttribute( entry, "givenName", "test99.givenName" );
        assertAttribute( entry, "userPassword", "secret" );
        assertAttribute( entry, "telephoneNumber", "11111", "22222" );
    }


    @Test
    public void testUpdate_addFields() throws Exception
    {
        InetOrgPerson user = dao.getById( "test2" );
        user.getTelephoneNumbers().add( "33333" );
        user.setFirstName( "test2.givenName" );
        user.setPassword( "test2.secret".getBytes( "UTF-8" ) );
        dao.update( user );

        DN dn = new DN( "uid=test2,ou=users,dc=example,dc=com" );
        assertTrue( service.getAdminSession().exists( dn ) );
        Entry entry = service.getAdminSession().lookup( dn );
        assertNotNull( entry );

        assertAttribute( entry, "objectClass", "inetOrgPerson", "organizationalPerson", "person", "top" );
        assertAttribute( entry, "uid", "test2" );
        assertAttribute( entry, "cn", "test2.cn" );
        assertAttribute( entry, "sn", "test2.sn" );
        assertAttribute( entry, "givenName", "test2.givenName" );
        assertAttribute( entry, "telephoneNumber", "33333" );
        assertAttribute( entry, "userPassword", "test2.secret" );
    }


    @Test
    public void testUpdate_deleteFields() throws Exception
    {
        InetOrgPerson user = dao.getById( "test1" );
        user.getTelephoneNumbers().clear();
        user.setFirstName( null );
        user.setPassword( null );
        dao.update( user );

        DN dn = new DN( "uid=test1,ou=users,dc=example,dc=com" );
        assertTrue( service.getAdminSession().exists( dn ) );
        Entry entry = service.getAdminSession().lookup( dn );
        assertNotNull( entry );

        assertAttribute( entry, "objectClass", "inetOrgPerson", "organizationalPerson", "person", "top" );
        assertAttribute( entry, "uid", "test1" );
        assertAttribute( entry, "cn", "test1.cn" );
        assertAttribute( entry, "sn", "test1.sn" );
        assertAttribute( entry, "givenName" );
        assertAttribute( entry, "telephoneNumber" );
        assertAttribute( entry, "userPassword" );
    }


    @Test
    public void testUpdate_setFields() throws Exception
    {
        InetOrgPerson user = dao.getById( "test1" );
        user.getTelephoneNumbers().clear();
        user.getTelephoneNumbers().add( "44444" );
        user.getTelephoneNumbers().add( "55555" );
        user.setFirstName( "user1.anotherGivenName" );
        user.setPassword( "simple".getBytes( "UTF-8" ) );
        dao.update( user );

        DN dn = new DN( "uid=test1,ou=users,dc=example,dc=com" );
        assertTrue( service.getAdminSession().exists( dn ) );
        Entry entry = service.getAdminSession().lookup( dn );
        assertNotNull( entry );

        assertAttribute( entry, "objectClass", "inetOrgPerson", "organizationalPerson", "person", "top" );
        assertAttribute( entry, "uid", "test1" );
        assertAttribute( entry, "cn", "test1.cn" );
        assertAttribute( entry, "sn", "test1.sn" );
        assertAttribute( entry, "givenName", "user1.anotherGivenName" );
        assertAttribute( entry, "telephoneNumber", "44444", "55555" );
        assertAttribute( entry, "userPassword", "simple" );
    }


    @Test
    public void testDelete() throws Exception
    {
        InetOrgPerson user = dao.getById( "test2" );
        dao.delete( user );

        DN dn = new DN( "uid=test2,ou=users,dc=example,dc=com" );
        assertFalse( service.getAdminSession().exists( dn ) );
    }


    private void assertAttribute( Entry entry, String attribute, String... values ) throws Exception
    {
        if ( values.length == 0 )
        {
            assertNull( entry.get( attribute ) );
        }
        else
        {
            assertNotNull( entry.get( attribute ) );
            assertEquals( values.length, entry.get( attribute ).size() );
            for ( String value : values )
            {
                assertTrue( entry.get( attribute ).contains( value )
                    || entry.get( attribute ).contains( value.getBytes( "UTF-8" ) ) );
            }
        }
    }
}
