In-depth guide

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer sapien purus, congue a tincidunt non, mollis vitae diam. Fusce pharetra massa interdum nisl ultrices mattis. Cras eget est ultricies magna auctor condimentum. Phasellus sed elit erat, a ultricies arcu. Proin vitae volutpat ante. Quisque porta tempor elit, non aliquam tortor sodales nec. Sed imperdiet metus sed orci adipiscing faucibus. Nulla facilisi. Nulla massa tortor, dapibus a vehicula vel, cursus vel mi. Integer sed lacus velit. Etiam dignissim varius turpis ut ullamcorper. Integer lobortis imperdiet dui, ut porta nulla interdum ac. Nulla neque lorem, bibendum et venenatis a, convallis in arcu. In sagittis neque ac mauris venenatis id elementum elit lacinia. Phasellus porttitor turpis eu ipsum pharetra eleifend. Integer ac magna sit amet mi luctus scelerisque at a odio. Quisque sed mi nibh, ac pretium felis. Nunc congue sapien sit amet ipsum elementum ac euismod justo mollis. Etiam dictum elementum libero sed sagittis.

2.1 Defining properties

Test properties follows these patterns:

                    
// connector configuration
                    
connector.{$property.name}="some value"
connector.connectionUrl="jdbc:mysql://localhost/database"


                    
// testsuite configuration
                    
testsuite.{$property.name}="some value"
testsuite.bundleJar=System.getProperty("bundleJar")


                    
// DataProvider configuration
                    
i{$iteration}.{$test.name}.{$oclass.name}.${attribute.name}="some value"
i0.Create.__ACCOUNT__.DEPARTMENT="some value"

                

 Note:

When defining properties, case sensitivity is important.

The property property.name is derived from the setXYZ() method, which is typically inside the FooConnectorConfiguration class.

Properties lookup

Properties are searched recursively. If a test looks for the property connector.i1.wrong.login and does not find its definition, it trys to look for i1.wrong.login. If nothing is found, it trys wrong.login and so on. After the last iteration, the GroovyDataProvider sets the default value according to the property type.

Property values are evaluated during the initial parsing of the property files. The only exception is if either the  Lazy.random()or Lazy.get() static util methods, which specify lazy evaluation, are used. If lazy evaluation is specified, a property value is not evaluated until the property is requested.

When a property value is recursively found, it is stored as the value of the property that was initially looked up. For instance, test looks for connector.i1.wrong.login and recursively finds the value of  wrong.login. This value will also be stored for connector.i1.wrong.login. The next time test looks for the original property ( connector.i1.wrong...), its value will be returned immediately.

Property value types

Any Java type can be used. (For more information, see standard imports.) If additional types are needed, these can be imported.  

Default value definitions for property types are:

Tstring = Lazy.random("AAAAA##")
Tinteger= Lazy.random("##", Integer.class)
Tint= Lazy.random("##", Integer.class)
Tlong= Lazy.random("#####", Long.class)
                    
// for full list of default values see /framework/contract-tests/src/bootstrap.groovy
                    
                

Properties that have not been defined are assigned default values by GroovyDataProvider. For example, the test looks for the undefined property i0.Create.__ACCOUNT__.DEPARTMENT as follows:

  1. The test uses recursive lookup and determines there is no such property 
  2. Because  i0.Create.__ACCOUNT__.DEPARTMENT is a String, we return the default value Tstring = Lazy.random("AAAAA##")
    The expression to the right of the = operator is dynamically evaluated and a random string is created following the pattern four uppercase letters concatenated with two digits (for example, JRIW87).

Macros

There are no macros used in the new Groovy Contract tests, however, if you would like to transfer your old configuration, a Translation table to Groovy is available.

2.2 Configuring tests

Required properties

Bundle JAR Path

After it is compiled, the path to the connector distribution JAR file is  ${project.name}/dist.

Use the System.getProperty() call to get the bundleJar system property that is set by ant.

testsuite.bundleJar=System.getProperty("bundleJar")
                    // bundleJar System property is
                         // set by ant to the current connector
                    
                    
                

Connector Configuration Properties

Properties declared in the connector configuration bean (Javadoc of Configuration interface) use the following pattern:
connector.${property.name}=${value}

                
where ${property.name} is a property of the configuration bean, and ${value} can be a value (any Java object).

The following table represents two equivalent forms of defining connector properties. In most cases the option on the right is favored.

                                        
// example configuration of database table connector
// for MySQL with sample values
                                        
connector.host="localhost"
connector.login="login"
connector.password="password"
connector.port="3306"
connector.driver="com.mysql.jdbc.Driver"
connector.usermodel="modeluser"
                                    
                                        
// example configuration of database table connector
// for MySQL with sample values
                                        
connector {
 host="localhost"
 login="login"
 password="password"
 port="3306"
 driver="com.mysql.jdbc.Driver"
 usermodel="modeluser"
}
                                        
// connector
                                        
                                    
Note: The prefix tree should be unique in the whole configuration file (in this case only one construct connector { ... } ) is present.

There are two classes of default values:

  1. Missing default values . Default values of properties: connector.*, and testsuite.* is defined as  ObjectNotFoundException for integer, float, double and string types to protect generation of random values when configuration property definition is missing.
  2. Supplied default values . Default values for iterative properties (for example,  connector.i1.wrong.host) are listed here (and in bootstrap.groovy):
                                
    /*
     * Default data values for iterative types
    */
                                
    Tstring = Lazy.random("AAAAA##")
    Tinteger = Lazy.random("##", Integer.class)
    Tint = Lazy.random("##", Integer.class)
    Tlong = Lazy.random("#####", Long.class)
    Tbiginteger = Lazy.random("#####", java.math.BigInteger.class)
    Tfloat = Lazy.random("#####\\.##", Float.class)
    Tdouble=Lazy.random("#####\\.##", Double.class)
    Tbigdecimal=Lazy.random("#####\\.##", java.math.BigDecimal.class)
    Tboolean=false
    Tbytearray=Lazy.random(".............", byte[].class)
    Tcharacter=Lazy.random(".", Character.class)
    
                                
    //Default data for multivalue attributes of common types
                                
    multi.Tstring=[Lazy.random("AAAAA##"), Lazy.random("AAAAA##")]
    multi.Tinteger=[Lazy.random("##",Integer.class), Lazy.random("##", Integer.class)]
    multi.Tlong=[Lazy.random("#####", Long.class), Lazy.random("#####", Long.class)]
    multi.Tbiginteger=[Lazy.random("#####", java.math.BigInteger.class), Lazy.random("#####", java.math.BigInteger.class)]
    multi.Tfloat=[Lazy.random("#####\\.##", Float.class), Lazy.random("#####\\.##", Float.class)]
    multi.Tdouble=[Lazy.random("#####\\.##", Double.class), Lazy.random("#####\\.##", Double.class)]
    multi.Tbigdecimal=[Lazy.random("#####\\.##", java.math.BigDecimal.class), Lazy.random("#####\\.##", java.math.BigDecimal.class)]
    multi.Tboolean=[false, false]
    multi.Tbytearray=[Lazy.random(".............", byte[].class), Lazy.random(".............", byte[].class)]
    multi.Tcharacter = [Lazy.random(".", Character.class), Lazy.random(".", Character.class)]
    
    //:~ default values
    
                            

AuthenticationApiOpTests properties

Authenticate tests in method testRun() need to know the attribute name, that contains the username. The username is later used to test authentication. The generic form of mandatory test property is:

import org.identityconnectors.contract.data.groovy.Lazy
testsuite.Authentication.
                        
${OBJECT_CLASS}
                        .username = Lazy.get("i0.Authentication.
                        
${OBJECT_CLASS}
                        .
                        
${USERNAME_ATTR_NAME}
                        ")
                    
Where the following parameters should be adjusted based on the Connector:
  • ${OBJECT_CLASS} - name of tested object class
  • ${USERNAME_ATTR_NAME} - name of attribute containing username

For example in databasetable connector we use the following setting for object class ACCOUNT and username attribute: testsuite.Authentication.__ACCOUNT__.username = Lazy.get("i0.Authentication.__ACCOUNT__.__NAME__")

ValidateApiOpTests Properties

For Validate tests, it is necessary to define a dedicated test property. The following example describes the layout of the property:
                    
// Connector WRONG configuration for ValidateApiOpTests
                    
testsuite.Validate.invalidConfig = [
    [ property1 : "invalidValue1" ],
    [ property2 : "invalidValue2" ],
    [ property3 : "invalidValue3", property4 : "invalidValue4" ] 
                    
// don't put more than 1 property per map
                    
]

                

Validate test property explained -- testsuite.Validate.invalidConfig contains a list of maps. The test procedure is the following: (1) choose a map from the list, (2) get the default connector configuration (defined by connector.*) and override the attributes that are given on the list. (3) perform Validate operation on connector. (4) repeat from step 1. with the next map from the list.

Best practice --

  • The test property enables to define multiple invalid properties, though best is to have once source of error (1 invalid property per map) at a time.
  • Validate tests focus mainly on empty or NULL configuration properties, validate test doesn't check if it is possible to connect with given configuration (source: ValidateApiOp Javadoc).

TestApiOpTests Properties

For TestApiOpTests, it is necessary to define a dedicated test property. The following example describes the layout of the property:
                    
// Connector WRONG configuration for TestApiOpTests
                    
testsuite.Test.invalidConfig = [
    [ attribute1 : "invalidValue1" ],
    [ attribute2 : "invalidValue2" ],
    [ attribute3 : "invalidValue3", attribute4 : "invalidValue4" ] 
                    
// don't put more than 1 property per map
                    
]

                

The layout of testsuite.Test.invalidConfig property is the same as for ValidateApiOpTests, however it has a different meaning.

Best practice --

  • The test property enables to define multiple invalid properties, though best is to have once source of error (1 invalid property per map) at a time.
  • TestApiOpTests focus mainly on incorrect configuration properties that prevent to connect to the resource. Candidates for wrong properties are non-existing host, port, username. For details see Javadoc of TestApiOp.

SchemaApiOpTests Properties

Define the list of supported object classes by connector (see Javadoc of ObjectClass for up-to-date object class types):

// database table connector supports only object class ACCOUNT testsuite.Schema.oclasses=['__ACCOUNT__'] Define the list of attributes per each supported object class: // list of ALL attributes of object class ACCOUNT for database table connector
testsuite.Schema.attributes.__ACCOUNT__.oclasses=['__NAME__', '__PASSWORD__', 'MANAGER', 'MIDDLENAME', (continued next line)
   'FIRSTNAME', 'LASTNAME', 'EMAIL', 'DEPARTMENT', 'TITLE', 'AGE', 'SALARY', 'JPEGPHOTO']


Define every attribute of each object class:
// definition of attribute __NAME__ of object class ACCOUNT for databse table connector
testsuite.Schema.__NAME__.attribute.__ACCOUNT__.oclasses=[
   type: java.lang.String.class,
   readable: true,
   createable: true,
   updateable: true,
   required: true,
   multiValue: false,
   returnedByDefault: true

]

Define supported object classes by operations. Must contain exactly the operations that are implemented by the connector:

// object classes supported by operations for database table connector
testsuite.Schema.operations=[
   GetApiOp: ['__ACCOUNT__'],
   SchemaApiOp: ['__ACCOUNT__'],
   ValidateApiOp: ['__ACCOUNT__'],
   CreateApiOp: ['__ACCOUNT__'],
   SearchApiOp: ['__ACCOUNT__'],
   DeleteApiOp: ['__ACCOUNT__'],
   ScriptOnConnectorApiOp: ['__ACCOUNT__'],
   UpdateApiOp: ['__ACCOUNT__'],
   AuthenticationApiOp: ['__ACCOUNT__'],
   TestApiOp: ['__ACCOUNT__'],
   SyncApiOp: []
]

Optional Properties

ScriptOnResourceApiOpTests, ScriptOnConnectorApiOpTests Properties

To be able to run ScriptOnResource and/or ScriptOnConnector tests, it is necessary to set the following properties. If some properties are not set, the test is skipped.
                    
// Example properties for ScriptOnResource test
                    
                    
                    
// Parameters required to construct ScriptContext object
                    

testsuite.ScriptOnResource.language="Groovy"
testsuite.ScriptOnResource.script="script text"


                    /* 
                    
                     // alternative: -- good for embedding scripts as a block:
                    
                     testsuite.ScriptOnResource.script='''
                    
                     multi line 
                    
                     script text
                    
                     '''
                    
                    */

testsuite.ScriptOnResource.arguments=[
 arg1: "value1",
 arg2: "22.5"}
]//map


                    // Expected return value of ScriptOnResource#runScriptOnResource method:
                    
                    // use suitable macro depending on what kind of object is expected
testsuite.ScriptOnResource.result=SomeObject

                
The ScriptOnResource operation also supports the following OperationOptions. If none are set,  OperationOptions is set to null. If one or both are set, OperationOptions are built.
testsuite.ScriptOnResource.RUN_AS_USER="user"
testsuite.ScriptOnResource.RUN_WITH_PASSWORD="password"

                

DataProvider Properties

These properties define which values should be generated for the attributes of the object classes that are returned in connector schema. These properties use the following pattern:
                    i{$identityNumber}.{$test.name}.{$oclass.name}.${attribute.name}=some value

                
Some tests (Search, Multi) create more than one identity per test:
  •   i{$identityNumber} is the serial number of the identity 
  • { $test.name} is the name of the test (Search, Create, ..., see *ApiOpTests#TEST_NAME
  • {$oclass.name} is the type of object class 
  • ${attribute.name} is name of an attribute of an object class

Examples:
                    // First created account by CreateApiOpTests will have FIRSTNAME attribute equal to FOO

                    i0.Create.__ACCOUNT__.FIRSTNAME="FOO"


                    // All accounts in SearchApiOpTests will have a LASTNAME attribute equal to BAR
Search.__ACCOUNT__.LASTNAME="BAR"


                    // All accounts in all tests will have PHONE attribute equal to 5-digit random values
__ACCOUNT__.PHONE=Lazy.random("######")

                

UpdateApiOpTests

In the UpdateApiOpTests is created identity updated with new values. The following properties are used for those new values:
${attribute.name}=${value}
                    // old value
modified.${attribute.name}=${value}
                    // new value
added.${attribute.name}=${value}
                    // new value added to a multivalue property (for example: list)
                    
                    // Example update LASTNAME attribute with value "sellers":
modified.LASTNAME="sellers"


                    // if you don't want to supply a new value for update, set it to the ObjectNotFoundException exception
modified.__NAME__=
                    ObjectNotFoundException()
                        // means that value is not supplied
                    

                

SearchApiOpTests#testCaseInsensitiveSearch

Case insensitive search tests are enabled by default, optional property testsuite.Search.disable.caseinsensitive is used for explicit setting.

Connector developer should make his/her choice, if the resource for the connector is case sensitive and set the tests appropriately.

/* USAGE */
testsuite.Search.disable.caseinsensitive = true // connector is case sensitive
testsuite.Search.disable.caseinsensitive = false // otherwise
                

SearchApiOpTests - compare existing objects in the resource by Uid only

During the search contract test all the objects which are stored in the resource before the test are retrieved (due to null filter search test) and later then the test checks that are still present and unchanged (since the test does not change them). However, in some cases changes made to different objects may affect already existing objects and their attributes. For this reason the connector developer may choose not to compare these objects as a whole (ConnectorObject) and rather compare them by Uids only.

By default the whole ConnectorObject is compared.

/* USAGE */
testsuite.Search.compareExistingObjectsByUidOnly = true		// compare existing objects by Uid only
testsuite.Search.compareExistingObjectsByUidOnly = false	// default
                

MultiOpTests#testLockOutOpAttribute()

To turn off testLockOutOpAttribute test add this line into config.groovy:
testsuite.Multi.skip.lockout=true
                

MultiOpTests#testDisableDateOpAttribute()

Test method testDisableDateOpAttribute uses two long date stamps for setting the __DISABLE_DATE__ specifal attribute (javadoc OperationalAttributes.DISABLE_DATE_NAME) in config.groovy:
// Long values
testsuite.Multi.__DISABLE_DATE__ = 123L
testsuite.Multi.modified.__DISABLE_DATE__ = 456L
                

In case of missing property definition default values are used (current timestamp, and 1/1/1970).