/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2017 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * Licensed 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 org.pentaho.di.trans.steps.dimensionlookup; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.UUID; import org.apache.commons.lang.StringUtils; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.pentaho.di.core.KettleEnvironment; import org.pentaho.di.core.SQLStatement; import org.pentaho.di.core.database.Database; import org.pentaho.di.core.database.DatabaseMeta; import org.pentaho.di.core.exception.KettleDatabaseException; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.logging.KettleLogStore; import org.pentaho.di.core.logging.LogChannelInterface; import org.pentaho.di.core.logging.LogChannelInterfaceFactory; import org.pentaho.di.core.logging.LoggingObjectInterface; import org.pentaho.di.core.plugins.PluginRegistry; import org.pentaho.di.core.row.RowMeta; import org.pentaho.di.core.row.RowMetaInterface; import org.pentaho.di.core.row.ValueMetaInterface; import org.pentaho.di.core.row.value.ValueMetaString; import org.pentaho.di.i18n.BaseMessages; import org.pentaho.di.repository.Repository; import org.pentaho.di.trans.TransMeta; import org.pentaho.di.trans.step.StepMeta; import org.pentaho.di.trans.step.StepMetaInterface; import org.pentaho.di.trans.steps.loadsave.LoadSaveTester; import org.pentaho.di.trans.steps.loadsave.initializer.InitializerInterface; import org.pentaho.di.trans.steps.loadsave.validator.ArrayLoadSaveValidator; import org.pentaho.di.trans.steps.loadsave.validator.DatabaseMetaLoadSaveValidator; import org.pentaho.di.trans.steps.loadsave.validator.FieldLoadSaveValidator; import org.pentaho.di.trans.steps.loadsave.validator.IntLoadSaveValidator; import org.pentaho.di.trans.steps.loadsave.validator.NonZeroIntLoadSaveValidator; import org.pentaho.di.trans.steps.loadsave.validator.PrimitiveIntArrayLoadSaveValidator; import org.pentaho.di.trans.steps.loadsave.validator.StringLoadSaveValidator; import org.pentaho.metastore.api.IMetaStore; public class DimensionLookupMetaTest implements InitializerInterface<StepMetaInterface> { LoadSaveTester loadSaveTester; Class<DimensionLookupMeta> testMetaClass = DimensionLookupMeta.class; private ThreadLocal<DimensionLookupMeta> holdTestingMeta = new ThreadLocal<DimensionLookupMeta>(); @Before public void setUpLoadSave() throws Exception { KettleEnvironment.init(); PluginRegistry.init( true ); List<String> attributes = Arrays.asList( "schemaName", "tableName", "update", "dateField", "dateFrom", "dateTo", "keyField", "keyRename", "autoIncrement", "versionField", "commitSize", "useBatchUpdate", "minYear", "maxYear", "techKeyCreation", "cacheSize", "usingStartDateAlternative", "startDateAlternative", "startDateFieldName", "preloadingCache", "keyStream", "keyLookup", "fieldStream", "fieldLookup", "fieldUpdate", "databaseMeta", "sequenceName" ); Map<String, String> getterMap = new HashMap<String, String>() { { put( "useBatchUpdate", "useBatchUpdate" ); } }; Map<String, String> setterMap = new HashMap<String, String>(); FieldLoadSaveValidator<String[]> stringArrayLoadSaveValidator = new ArrayLoadSaveValidator<String>( new StringLoadSaveValidator(), 5 ); Map<String, FieldLoadSaveValidator<?>> attrValidatorMap = new HashMap<String, FieldLoadSaveValidator<?>>(); attrValidatorMap.put( "keyStream", stringArrayLoadSaveValidator ); attrValidatorMap.put( "keyLookup", stringArrayLoadSaveValidator ); attrValidatorMap.put( "fieldStream", stringArrayLoadSaveValidator ); attrValidatorMap.put( "fieldLookup", stringArrayLoadSaveValidator ); // Note - have to use the non-zero int load/save validator here because if "update" // is false, code in DimensionLookupMeta replaces "ValueMetaInterface.TYPE_NONE" with // ValueMetaInterface.TYPE_STRING. This happens about once out of every 3 or so runs of // the test which made it a bit difficult to track down. // MB - 5/2016 attrValidatorMap.put( "fieldUpdate", new FieldUpdateIntArrayLoadSaveValidator( new NonZeroIntLoadSaveValidator( DimensionLookupMeta.typeDesc.length ), 5 ) ); attrValidatorMap.put( "databaseMeta", new DatabaseMetaLoadSaveValidator() ); attrValidatorMap.put( "startDateAlternative", new IntLoadSaveValidator( DimensionLookupMeta.getStartDateAlternativeCodes().length ) ); attrValidatorMap.put( "sequenceName", new SequenceNameLoadSaveValidator() ); Map<String, FieldLoadSaveValidator<?>> typeValidatorMap = new HashMap<String, FieldLoadSaveValidator<?>>(); loadSaveTester = new LoadSaveTester( testMetaClass, attributes, new ArrayList<String>(), new ArrayList<String>(), getterMap, setterMap, attrValidatorMap, typeValidatorMap, this ); } // Call the allocate method on the LoadSaveTester meta class @Override public void modify( StepMetaInterface someMeta ) { if ( someMeta instanceof DimensionLookupMeta ) { ( (DimensionLookupMeta) someMeta ).allocate( 5, 5 ); // doing this as a work-around for sequenceName validation. // Apparently, sequenceName will always be written (getXml), // but will only be read if the value of "update" is true. // While testing the load/save behavior, there is no sane way // to test dependent variables like this (that I could see). So, // I'm holding onto the meta, and will have a special load/save handler // for sequenceName. // MB - 5/2016 this.holdTestingMeta.set( (DimensionLookupMeta) someMeta ); } } @Test public void testSerialization() throws KettleException { loadSaveTester.testSerialization(); } public static final String databaseXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<connection>" + "<name>lookup</name>" + "<server>127.0.0.1</server>" + "<type>H2</type>" + "<access>Native</access>" + "<database>mem:db</database>" + "<port></port>" + "<username>sa</username>" + "<password></password>" + "</connection>"; @Before public void setUp() throws Exception { LogChannelInterfaceFactory logChannelInterfaceFactory = mock( LogChannelInterfaceFactory.class ); LogChannelInterface logChannelInterface = mock( LogChannelInterface.class ); KettleLogStore.setLogChannelInterfaceFactory( logChannelInterfaceFactory ); when( logChannelInterfaceFactory.create( any(), any( LoggingObjectInterface.class ) ) ).thenReturn( logChannelInterface ); } @Test public void testGetFields() throws Exception { RowMeta extraFields = new RowMeta(); extraFields.addValueMeta( new ValueMetaString( "field1" ) ); DatabaseMeta dbMeta = mock( DatabaseMeta.class ); DimensionLookupMeta meta = spy( new DimensionLookupMeta() ); meta.setUpdate( false ); meta.setKeyField( null ); meta.setFieldLookup( new String[] { "field1" } ); meta.setFieldStream( new String[] { "" } ); meta.setDatabaseMeta( dbMeta ); doReturn( extraFields ).when( meta ).getDatabaseTableFields( (Database) anyObject(), anyString(), anyString() ); doReturn( mock( LogChannelInterface.class ) ).when( meta ).getLog(); RowMeta row = new RowMeta(); try { meta.getFields( row, "DimensionLookupMetaTest", new RowMeta[] { row }, null, null, null, null ); } catch ( Throwable e ) { Assert.assertTrue( e.getMessage().contains( BaseMessages.getString( DimensionLookupMeta.class, "DimensionLookupMeta.Error.NoTechnicalKeySpecified" ) ) ); } } @Test public void testUseDefaultSchemaName() throws Exception { KettleEnvironment.init(); String schemaName = ""; String tableName = "tableName"; String schemaTable = "default.tableName"; String keyField = "keyField"; DatabaseMeta databaseMeta = spy( new DatabaseMeta( databaseXML ) { @Override public String getFieldDefinition( ValueMetaInterface v, String tk, String pk, boolean use_autoinc ) { return "someValue"; } } ); when( databaseMeta.getQuotedSchemaTableCombination( schemaName, tableName ) ).thenReturn( schemaTable ); DimensionLookupMeta dlm = new DimensionLookupMeta(); dlm.setUpdate( true ); dlm.setDatabaseMeta( databaseMeta ); dlm.setTableName( tableName ); dlm.setSchemaName( schemaName ); dlm.setKeyLookup( new String[] { "keyLookup1", "keyLookup2" } ); dlm.setKeyStream( new String[] { "keyStream1", "keyStream2" } ); dlm.setFieldLookup( new String[] { "fieldLookup1", "fieldLookup2" } ); dlm.setFieldStream( new String[] { "FieldStream1", "FieldStream2" } ); dlm.setFieldUpdate( new int[] { 1, 2 } ); dlm.setKeyField( keyField ); StepMeta stepMeta = mock( StepMeta.class ); RowMetaInterface rowMetaInterface = mock( RowMetaInterface.class ); when( rowMetaInterface.size() ).thenReturn( 1 ); Repository repository = mock( Repository.class ); IMetaStore metaStore = mock( IMetaStore.class ); SQLStatement sqlStatement = dlm.getSQLStatements( new TransMeta(), stepMeta, rowMetaInterface, repository, metaStore ); String sql = sqlStatement.getSQL(); assertEquals( 3, StringUtils.countMatches( sql, schemaTable ) ); } @Test public void testProvidesModelerMeta() throws Exception { final RowMeta rowMeta = Mockito.mock( RowMeta.class ); final DimensionLookupMeta dimensionLookupMeta = new DimensionLookupMeta() { @Override Database createDatabaseObject() { return mock( Database.class ); } @Override protected RowMetaInterface getDatabaseTableFields( Database db, String schemaName, String tableName ) throws KettleDatabaseException { assertEquals( "aSchema", schemaName ); assertEquals( "aDimTable", tableName ); return rowMeta; } }; dimensionLookupMeta.setFieldLookup( new String[] { "f1", "f2", "f3" } ); dimensionLookupMeta.setKeyLookup( new String[] {"k1"} ); dimensionLookupMeta.setFieldStream( new String[] { "s4", "s5", "s6" } ); dimensionLookupMeta.setKeyStream( new String[] {"ks1"} ); dimensionLookupMeta.setSchemaName( "aSchema" ); dimensionLookupMeta.setTableName( "aDimTable" ); final DimensionLookupData dimensionLookupData = new DimensionLookupData(); assertEquals( rowMeta, dimensionLookupMeta.getRowMeta( dimensionLookupData ) ); assertEquals( 4, dimensionLookupMeta.getDatabaseFields().size() ); assertEquals( "f1", dimensionLookupMeta.getDatabaseFields().get( 0 ) ); assertEquals( "f2", dimensionLookupMeta.getDatabaseFields().get( 1 ) ); assertEquals( "f3", dimensionLookupMeta.getDatabaseFields().get( 2 ) ); assertEquals( "k1", dimensionLookupMeta.getDatabaseFields().get( 3 ) ); assertEquals( 4, dimensionLookupMeta.getStreamFields().size() ); assertEquals( "s4", dimensionLookupMeta.getStreamFields().get( 0 ) ); assertEquals( "s5", dimensionLookupMeta.getStreamFields().get( 1 ) ); assertEquals( "s6", dimensionLookupMeta.getStreamFields().get( 2 ) ); assertEquals( "ks1", dimensionLookupMeta.getStreamFields().get( 3 ) ); } // Note - Removed cloneTest since it's covered by the load/save tester // Doing this as a work-around for sequenceName validation. // Apparently, sequenceName will always be written (getXml), // but will only be read if the value of "update" is true (readData). // While testing the load/save behavior, there is no sane way // to test dependent variables like this (that I could see). So, // I'm holding onto the meta in a threadlocal, and have to have // this special load/save handler for sequenceName. // MB - 5/2016 public class SequenceNameLoadSaveValidator implements FieldLoadSaveValidator<String> { final Random rand = new Random(); @Override public String getTestObject() { DimensionLookupMeta dlm = holdTestingMeta.get(); // get the currently-being tested meta if ( dlm.isUpdate() ) { // value returned here is dependant on isUpdate() return UUID.randomUUID().toString(); // return a string } else { return null; // Return null if !isUpdate ... } } @Override public boolean validateTestObject( String testObject, Object actual ) { String another = (String) actual; DimensionLookupMeta dlm = holdTestingMeta.get(); if ( dlm.isUpdate() ) { return testObject.equals( another ); // if isUpdate, compare strings } else { return ( another == null ); // If !isUpdate, another should be null } } } public class FieldUpdateIntArrayLoadSaveValidator extends PrimitiveIntArrayLoadSaveValidator { public FieldUpdateIntArrayLoadSaveValidator( FieldLoadSaveValidator<Integer> fieldValidator ) { this( fieldValidator, null ); } public FieldUpdateIntArrayLoadSaveValidator( FieldLoadSaveValidator<Integer> fieldValidator, Integer elements ) { super( fieldValidator, elements ); } @Override public int[] getTestObject() { DimensionLookupMeta dlm = holdTestingMeta.get(); int[] testObject = super.getTestObject(); if ( !dlm.isUpdate() ) { dlm.setReturnType( testObject ); } return testObject; } } }