embulk-input-marketo_extended 0.6.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/.github/PULL_REQUEST_TEMPLATE.md +37 -0
  3. data/.gitignore +14 -0
  4. data/.travis.yml +6 -0
  5. data/CHANGELOG.md +170 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +213 -0
  8. data/build.gradle +103 -0
  9. data/config/checkstyle/checkstyle.xml +128 -0
  10. data/config/checkstyle/default.xml +108 -0
  11. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  12. data/gradle/wrapper/gradle-wrapper.properties +6 -0
  13. data/gradlew +169 -0
  14. data/gradlew.bat +84 -0
  15. data/lib/embulk/input/marketo.rb +3 -0
  16. data/settings.gradle +1 -0
  17. data/src/main/java/org/embulk/input/marketo/CsvTokenizer.java +700 -0
  18. data/src/main/java/org/embulk/input/marketo/MarketoInputPlugin.java +15 -0
  19. data/src/main/java/org/embulk/input/marketo/MarketoInputPluginDelegate.java +100 -0
  20. data/src/main/java/org/embulk/input/marketo/MarketoService.java +38 -0
  21. data/src/main/java/org/embulk/input/marketo/MarketoServiceImpl.java +245 -0
  22. data/src/main/java/org/embulk/input/marketo/MarketoUtils.java +212 -0
  23. data/src/main/java/org/embulk/input/marketo/delegate/ActivityBulkExtractInputPlugin.java +167 -0
  24. data/src/main/java/org/embulk/input/marketo/delegate/CampaignInputPlugin.java +48 -0
  25. data/src/main/java/org/embulk/input/marketo/delegate/CustomObjectInputPlugin.java +75 -0
  26. data/src/main/java/org/embulk/input/marketo/delegate/CustomObjectResponseMapperBuilder.java +81 -0
  27. data/src/main/java/org/embulk/input/marketo/delegate/LeadBulkExtractInputPlugin.java +66 -0
  28. data/src/main/java/org/embulk/input/marketo/delegate/LeadServiceResponseMapperBuilder.java +85 -0
  29. data/src/main/java/org/embulk/input/marketo/delegate/LeadWithListInputPlugin.java +64 -0
  30. data/src/main/java/org/embulk/input/marketo/delegate/LeadWithProgramInputPlugin.java +60 -0
  31. data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPlugin.java +441 -0
  32. data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseInputPluginDelegate.java +92 -0
  33. data/src/main/java/org/embulk/input/marketo/delegate/ProgramInputPlugin.java +228 -0
  34. data/src/main/java/org/embulk/input/marketo/exception/MarketoAPIException.java +30 -0
  35. data/src/main/java/org/embulk/input/marketo/model/BulkExtractRangeHeader.java +26 -0
  36. data/src/main/java/org/embulk/input/marketo/model/MarketoAccessTokenResponse.java +92 -0
  37. data/src/main/java/org/embulk/input/marketo/model/MarketoBulkExtractRequest.java +68 -0
  38. data/src/main/java/org/embulk/input/marketo/model/MarketoError.java +40 -0
  39. data/src/main/java/org/embulk/input/marketo/model/MarketoField.java +126 -0
  40. data/src/main/java/org/embulk/input/marketo/model/MarketoResponse.java +82 -0
  41. data/src/main/java/org/embulk/input/marketo/model/filter/DateRangeFilter.java +40 -0
  42. data/src/main/java/org/embulk/input/marketo/rest/MarketoBaseRestClient.java +306 -0
  43. data/src/main/java/org/embulk/input/marketo/rest/MarketoInputStreamResponseEntityReader.java +69 -0
  44. data/src/main/java/org/embulk/input/marketo/rest/MarketoRESTEndpoint.java +47 -0
  45. data/src/main/java/org/embulk/input/marketo/rest/MarketoResponseJetty92EntityReader.java +89 -0
  46. data/src/main/java/org/embulk/input/marketo/rest/MarketoRestClient.java +569 -0
  47. data/src/main/java/org/embulk/input/marketo/rest/RecordPagingIterable.java +180 -0
  48. data/src/test/java/org/embulk/input/marketo/MarketoServiceImplTest.java +140 -0
  49. data/src/test/java/org/embulk/input/marketo/MarketoUtilsTest.java +87 -0
  50. data/src/test/java/org/embulk/input/marketo/delegate/ActivityBulkExtractInputPluginTest.java +128 -0
  51. data/src/test/java/org/embulk/input/marketo/delegate/CampaignInputPluginTest.java +73 -0
  52. data/src/test/java/org/embulk/input/marketo/delegate/CustomObjectInputPluginTest.java +102 -0
  53. data/src/test/java/org/embulk/input/marketo/delegate/LeadBulkExtractInputPluginTest.java +99 -0
  54. data/src/test/java/org/embulk/input/marketo/delegate/LeadServiceResponseMapperBuilderTest.java +119 -0
  55. data/src/test/java/org/embulk/input/marketo/delegate/LeadWithListInputPluginTest.java +101 -0
  56. data/src/test/java/org/embulk/input/marketo/delegate/LeadWithProgramInputPluginTest.java +103 -0
  57. data/src/test/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPluginTest.java +169 -0
  58. data/src/test/java/org/embulk/input/marketo/delegate/ProgramInputPluginTest.java +343 -0
  59. data/src/test/java/org/embulk/input/marketo/rest/MarketoBaseRestClientTest.java +368 -0
  60. data/src/test/java/org/embulk/input/marketo/rest/MarketoRestClientTest.java +584 -0
  61. data/src/test/resources/config/activity_bulk_extract_config.yaml +7 -0
  62. data/src/test/resources/config/custom_object_config.yaml +8 -0
  63. data/src/test/resources/config/lead_bulk_extract_config.yaml +8 -0
  64. data/src/test/resources/config/rest_config.yaml +3 -0
  65. data/src/test/resources/fixtures/activity_extract1.csv +35 -0
  66. data/src/test/resources/fixtures/activity_extract2.csv +22 -0
  67. data/src/test/resources/fixtures/activity_types.json +22 -0
  68. data/src/test/resources/fixtures/all_program_full.json +53 -0
  69. data/src/test/resources/fixtures/campaign_response.json +38 -0
  70. data/src/test/resources/fixtures/campaign_response_full.json +102 -0
  71. data/src/test/resources/fixtures/custom_object_describe.json +124 -0
  72. data/src/test/resources/fixtures/custom_object_describe_marketo_fields_full.json +22 -0
  73. data/src/test/resources/fixtures/custom_object_expected.json +66 -0
  74. data/src/test/resources/fixtures/custom_object_response.json +24 -0
  75. data/src/test/resources/fixtures/custom_object_response_full.json +23 -0
  76. data/src/test/resources/fixtures/lead_by_list.json +33 -0
  77. data/src/test/resources/fixtures/lead_by_program_response.json +47 -0
  78. data/src/test/resources/fixtures/lead_describe.json +221 -0
  79. data/src/test/resources/fixtures/lead_describe_expected.json +66 -0
  80. data/src/test/resources/fixtures/lead_describe_marketo_fields_full.json +518 -0
  81. data/src/test/resources/fixtures/lead_extract1.csv +11 -0
  82. data/src/test/resources/fixtures/lead_response_full.json +2402 -0
  83. data/src/test/resources/fixtures/lead_with_program_full.json +17 -0
  84. data/src/test/resources/fixtures/leads_extract2.csv +10 -0
  85. data/src/test/resources/fixtures/list_reponse_full.json +191 -0
  86. data/src/test/resources/fixtures/lists_response.json +31 -0
  87. data/src/test/resources/fixtures/program_response.json +71 -0
  88. metadata +171 -0
@@ -0,0 +1,103 @@
1
+ package org.embulk.input.marketo.delegate;
2
+
3
+ import com.fasterxml.jackson.databind.JavaType;
4
+ import com.fasterxml.jackson.databind.ObjectMapper;
5
+ import com.fasterxml.jackson.databind.node.ObjectNode;
6
+ import org.apache.commons.lang3.StringUtils;
7
+ import org.embulk.EmbulkTestRuntime;
8
+ import org.embulk.base.restclient.ServiceResponseMapper;
9
+ import org.embulk.base.restclient.record.RecordImporter;
10
+ import org.embulk.base.restclient.record.ValueLocator;
11
+ import org.embulk.config.ConfigLoader;
12
+ import org.embulk.config.ConfigSource;
13
+ import org.embulk.input.marketo.MarketoUtils;
14
+ import org.embulk.input.marketo.model.MarketoField;
15
+ import org.embulk.input.marketo.rest.MarketoRestClient;
16
+ import org.embulk.input.marketo.rest.RecordPagingIterable;
17
+ import org.embulk.spi.PageBuilder;
18
+ import org.embulk.spi.Schema;
19
+ import org.junit.Before;
20
+ import org.junit.Rule;
21
+ import org.junit.Test;
22
+ import org.mockito.ArgumentCaptor;
23
+ import org.mockito.Mockito;
24
+
25
+ import java.io.IOException;
26
+ import java.util.ArrayList;
27
+ import java.util.List;
28
+
29
+ import static org.junit.Assert.assertEquals;
30
+ import static org.mockito.ArgumentMatchers.any;
31
+ import static org.mockito.ArgumentMatchers.anyString;
32
+ import static org.mockito.ArgumentMatchers.eq;
33
+
34
+ /**
35
+ * Created by tai.khuu on 10/10/17.
36
+ */
37
+ public class LeadWithProgramInputPluginTest
38
+ {
39
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
40
+
41
+ @Rule
42
+ public EmbulkTestRuntime embulkTestRuntime = new EmbulkTestRuntime();
43
+
44
+ private ConfigSource configSource;
45
+
46
+ private LeadWithProgramInputPlugin leadWithProgramInputPlugin;
47
+
48
+ private MarketoRestClient mockMarketoRestClient;
49
+
50
+ @Before
51
+ public void setUp() throws Exception
52
+ {
53
+ leadWithProgramInputPlugin = Mockito.spy(new LeadWithProgramInputPlugin());
54
+ ConfigLoader configLoader = embulkTestRuntime.getInjector().getInstance(ConfigLoader.class);
55
+ configSource = configLoader.fromYaml(this.getClass().getResourceAsStream("/config/rest_config.yaml"));
56
+ mockMarketoRestClient = Mockito.mock(MarketoRestClient.class);
57
+ Mockito.doReturn(mockMarketoRestClient).when(leadWithProgramInputPlugin).createMarketoRestClient(any(LeadWithProgramInputPlugin.PluginTask.class));
58
+ }
59
+
60
+ @Test
61
+ public void testRun() throws IOException
62
+ {
63
+ RecordPagingIterable<ObjectNode> mockLeadRecordPagingIterable = Mockito.mock(RecordPagingIterable.class);
64
+ RecordPagingIterable<ObjectNode> mockLeadEmptyRecordPagingIterable = Mockito.mock(RecordPagingIterable.class);
65
+ RecordPagingIterable<ObjectNode> mockProgramRecords = Mockito.mock(RecordPagingIterable.class);
66
+
67
+ Mockito.when(mockLeadEmptyRecordPagingIterable.iterator()).thenReturn(new ArrayList<ObjectNode>().iterator());
68
+ JavaType objectNodeListType = OBJECT_MAPPER.getTypeFactory().constructParametrizedType(List.class, List.class, ObjectNode.class);
69
+ JavaType marketoFieldsType = OBJECT_MAPPER.getTypeFactory().constructParametrizedType(List.class, List.class, MarketoField.class);
70
+ List<ObjectNode> leads = OBJECT_MAPPER.readValue(this.getClass().getResourceAsStream("/fixtures/lead_with_program_full.json"), objectNodeListType);
71
+ List<ObjectNode> programs = OBJECT_MAPPER.readValue(this.getClass().getResourceAsStream("/fixtures/all_program_full.json"), objectNodeListType);
72
+ Mockito.when(mockProgramRecords.iterator()).thenReturn(programs.iterator());
73
+ List<MarketoField> marketoFields = OBJECT_MAPPER.readValue(this.getClass().getResourceAsStream("/fixtures/lead_describe_marketo_fields_full.json"), marketoFieldsType);
74
+ Mockito.when(mockLeadRecordPagingIterable.iterator()).thenReturn(leads.iterator());
75
+ Mockito.when(mockMarketoRestClient.describeLead()).thenReturn(marketoFields);
76
+ Mockito.when(mockMarketoRestClient.getPrograms()).thenReturn(mockProgramRecords);
77
+ List<String> fieldNameFromMarketoFields = MarketoUtils.getFieldNameFromMarketoFields(marketoFields);
78
+ String fieldNameString = StringUtils.join(fieldNameFromMarketoFields, ",");
79
+ Mockito.when(mockMarketoRestClient.getLeadsByProgram(anyString(), eq(fieldNameString))).thenReturn(mockLeadEmptyRecordPagingIterable);
80
+ Mockito.when(mockMarketoRestClient.getLeadsByProgram("1003", fieldNameString)).thenReturn(mockLeadRecordPagingIterable);
81
+
82
+ LeadWithProgramInputPlugin.PluginTask task = configSource.loadConfig(LeadWithProgramInputPlugin.PluginTask.class);
83
+ ServiceResponseMapper<? extends ValueLocator> mapper = leadWithProgramInputPlugin.buildServiceResponseMapper(task);
84
+
85
+ RecordImporter recordImporter = mapper.createRecordImporter();
86
+ PageBuilder mockPageBuilder = Mockito.mock(PageBuilder.class);
87
+
88
+ leadWithProgramInputPlugin.ingestServiceData(task, recordImporter, 1, mockPageBuilder);
89
+ Mockito.verify(mockMarketoRestClient, Mockito.times(1)).getPrograms();
90
+ Mockito.verify(mockMarketoRestClient, Mockito.times(3)).getLeadsByProgram(anyString(), eq(fieldNameString));
91
+ Mockito.verify(mockMarketoRestClient, Mockito.times(1)).describeLead();
92
+
93
+ Schema embulkSchema = mapper.getEmbulkSchema();
94
+ ArgumentCaptor<Long> longArgumentCaptor = ArgumentCaptor.forClass(Long.class);
95
+
96
+ Mockito.verify(mockPageBuilder, Mockito.times(1)).setLong(eq(embulkSchema.lookupColumn("mk_id")), longArgumentCaptor.capture());
97
+ Mockito.verify(mockPageBuilder, Mockito.times(1)).setString(eq(embulkSchema.lookupColumn("mk_programId")), eq("1003"));
98
+
99
+ List<Long> allValues = longArgumentCaptor.getAllValues();
100
+ long actualValue = allValues.get(0);
101
+ assertEquals(102519L, actualValue);
102
+ }
103
+ }
@@ -0,0 +1,169 @@
1
+ package org.embulk.input.marketo.delegate;
2
+
3
+ import com.google.common.base.Optional;
4
+
5
+ import org.embulk.EmbulkTestRuntime;
6
+ import org.embulk.config.ConfigDiff;
7
+ import org.embulk.config.ConfigException;
8
+ import org.embulk.config.ConfigLoader;
9
+ import org.embulk.config.ConfigSource;
10
+ import org.embulk.config.TaskReport;
11
+ import org.embulk.input.marketo.MarketoInputPluginDelegate;
12
+ import org.embulk.input.marketo.MarketoUtils;
13
+ import org.embulk.spi.Schema;
14
+ import org.joda.time.DateTime;
15
+ import org.junit.Before;
16
+ import org.junit.Rule;
17
+ import org.junit.Test;
18
+ import org.mockito.ArgumentCaptor;
19
+ import org.mockito.Mockito;
20
+
21
+ import java.io.IOException;
22
+ import java.text.DateFormat;
23
+ import java.text.SimpleDateFormat;
24
+ import java.util.Arrays;
25
+ import java.util.Date;
26
+
27
+ import static org.junit.Assert.assertEquals;
28
+ import static org.junit.Assert.fail;
29
+ import static org.mockito.AdditionalAnswers.delegatesTo;
30
+ import static org.mockito.Mockito.mock;
31
+ import static org.mockito.Mockito.when;
32
+
33
+ /**
34
+ * Created by khuutantaitai on 10/3/17.
35
+ */
36
+ public class MarketoBaseBulkExtractInputPluginTest
37
+ {
38
+ @Rule
39
+ public EmbulkTestRuntime embulkTestRuntime = new EmbulkTestRuntime();
40
+
41
+ private MarketoBaseBulkExtractInputPlugin<MarketoBaseBulkExtractInputPlugin.PluginTask> baseBulkExtractInputPlugin;
42
+ private MarketoBaseBulkExtractInputPlugin.PluginTask validBaseTask;
43
+
44
+ @Before
45
+ public void prepare() throws IOException
46
+ {
47
+ baseBulkExtractInputPlugin = Mockito.mock(MarketoBaseBulkExtractInputPlugin.class, Mockito.CALLS_REAL_METHODS);
48
+ ConfigLoader configLoader = embulkTestRuntime.getInjector().getInstance(ConfigLoader.class);
49
+ ConfigSource configSource = configLoader.fromYaml(
50
+ this.getClass().getResourceAsStream("/config/activity_bulk_extract_config.yaml"));
51
+ validBaseTask = configSource.loadConfig(MarketoBaseBulkExtractInputPlugin.PluginTask.class);
52
+ }
53
+
54
+ @Test(expected = ConfigException.class)
55
+ public void validateInputTaskError()
56
+ {
57
+ MarketoBaseBulkExtractInputPlugin.PluginTask pluginTask = Mockito.mock(MarketoBaseBulkExtractInputPlugin.PluginTask.class);
58
+ Mockito.when(pluginTask.getFromDate()).thenReturn(null);
59
+ baseBulkExtractInputPlugin.validateInputTask(pluginTask);
60
+ }
61
+
62
+ @Test(expected = ConfigException.class)
63
+ public void invalidInputTaskWhenIncrementalByUpdatedAt()
64
+ {
65
+ MarketoBaseBulkExtractInputPlugin.PluginTask task = mock(
66
+ MarketoBaseBulkExtractInputPlugin.PluginTask.class,
67
+ delegatesTo(validBaseTask));
68
+ when(task.getIncrementalColumn()).thenReturn(Optional.of("updatedAt"));
69
+ when(task.getIncremental()).thenReturn(true);
70
+ baseBulkExtractInputPlugin.validateInputTask(task);
71
+ }
72
+
73
+ @Test
74
+ public void validInputTaskWhenIncrementalOtherThanUpdatedAt()
75
+ {
76
+ MarketoBaseBulkExtractInputPlugin.PluginTask task = mock(
77
+ MarketoBaseBulkExtractInputPlugin.PluginTask.class,
78
+ delegatesTo(validBaseTask));
79
+ when(task.getIncremental()).thenReturn(true);
80
+ when(task.getIncrementalColumn()).thenReturn(Optional.of("anythingButUpdatedAt"));
81
+ baseBulkExtractInputPlugin.validateInputTask(task); // should not throw
82
+ }
83
+
84
+ @Test
85
+ public void validInputTaskWhenNonIncrementalWhileSetUpdatedAt()
86
+ {
87
+ MarketoBaseBulkExtractInputPlugin.PluginTask task = mock(
88
+ MarketoBaseBulkExtractInputPlugin.PluginTask.class,
89
+ delegatesTo(validBaseTask));
90
+ when(task.getIncremental()).thenReturn(false);
91
+ when(task.getIncrementalColumn()).thenReturn(Optional.of("updatedAt"));
92
+ baseBulkExtractInputPlugin.validateInputTask(task); // should not throw
93
+ }
94
+
95
+ @Test()
96
+ public void validateInputTaskToDateLessThanJobStartTime()
97
+ {
98
+ Date fromDate = new Date(1504224000000L);
99
+ DateTime jobStartTime = new DateTime(1506842144000L);
100
+ MarketoBaseBulkExtractInputPlugin.PluginTask pluginTask = Mockito.mock(MarketoBaseBulkExtractInputPlugin.PluginTask.class);
101
+ Mockito.when(pluginTask.getFromDate()).thenReturn(fromDate);
102
+ Mockito.when(pluginTask.getJobStartTime()).thenReturn(jobStartTime);
103
+ Mockito.when(pluginTask.getFetchDays()).thenReturn(7);
104
+ baseBulkExtractInputPlugin.validateInputTask(pluginTask);
105
+ ArgumentCaptor<Optional<Date>> argumentCaptor = ArgumentCaptor.forClass(Optional.class);
106
+ Mockito.verify(pluginTask, Mockito.times(1)).setToDate(argumentCaptor.capture());
107
+ assertEquals(1504828800000L, argumentCaptor.getValue().get().getTime());
108
+ }
109
+
110
+ @Test()
111
+ public void validateInputTaskFromDateMoreThanJobStartTime()
112
+ {
113
+ Date fromDate = new Date(1507619744000L);
114
+ DateTime jobStartTime = new DateTime(1506842144000L);
115
+ MarketoBaseBulkExtractInputPlugin.PluginTask pluginTask = Mockito.mock(MarketoBaseBulkExtractInputPlugin.PluginTask.class);
116
+ Mockito.when(pluginTask.getFromDate()).thenReturn(fromDate);
117
+ Mockito.when(pluginTask.getJobStartTime()).thenReturn(jobStartTime);
118
+
119
+ try {
120
+ baseBulkExtractInputPlugin.validateInputTask(pluginTask);
121
+ }
122
+ catch (ConfigException ex) {
123
+ return;
124
+ }
125
+ fail();
126
+ }
127
+
128
+ @Test()
129
+ public void validateInputTaskToDateMoreThanJobStartTime()
130
+ {
131
+ Date fromDate = new Date(1504224000000L);
132
+ DateTime jobStartTime = new DateTime(1504396800000L);
133
+ MarketoBaseBulkExtractInputPlugin.PluginTask pluginTask = Mockito.mock(MarketoBaseBulkExtractInputPlugin.PluginTask.class);
134
+ Mockito.when(pluginTask.getFromDate()).thenReturn(fromDate);
135
+ Mockito.when(pluginTask.getFetchDays()).thenReturn(7);
136
+ Mockito.when(pluginTask.getJobStartTime()).thenReturn(jobStartTime);
137
+ baseBulkExtractInputPlugin.validateInputTask(pluginTask);
138
+ ArgumentCaptor<Optional<Date>> toDateArgumentCaptor = ArgumentCaptor.forClass(Optional.class);
139
+ Mockito.verify(pluginTask, Mockito.times(1)).setToDate(toDateArgumentCaptor.capture());
140
+ assertEquals(jobStartTime.getMillis(), toDateArgumentCaptor.getValue().get().getTime());
141
+ }
142
+
143
+ @Test
144
+ public void getToDate() throws Exception
145
+ {
146
+ DateTime date = new DateTime(1505033728000L);
147
+ DateTime jobStartTime = new DateTime(1507625728000L);
148
+ MarketoBaseBulkExtractInputPlugin.PluginTask pluginTask = Mockito.mock(MarketoInputPluginDelegate.PluginTask.class);
149
+ Mockito.when(pluginTask.getFromDate()).thenReturn(date.toDate());
150
+ Mockito.when(pluginTask.getFetchDays()).thenReturn(30);
151
+ Mockito.when(pluginTask.getJobStartTime()).thenReturn(jobStartTime);
152
+ DateTime toDate = baseBulkExtractInputPlugin.getToDate(pluginTask);
153
+ assertEquals(toDate, jobStartTime);
154
+ }
155
+
156
+ @Test
157
+ public void buildConfigDiff() throws Exception
158
+ {
159
+ TaskReport taskReport1 = Mockito.mock(TaskReport.class);
160
+ MarketoInputPluginDelegate.PluginTask task = Mockito.mock(MarketoInputPluginDelegate.PluginTask.class);
161
+ Mockito.when(task.getIncremental()).thenReturn(true);
162
+ Mockito.when(task.getIncrementalColumn()).thenReturn(Optional.of("createdAt"));
163
+ Date toDate = new Date(1507625728000L);
164
+ Mockito.when(task.getToDate()).thenReturn(Optional.of(toDate));
165
+ ConfigDiff configDiff = baseBulkExtractInputPlugin.buildConfigDiff(task, Mockito.mock(Schema.class), 1, Arrays.asList(taskReport1));
166
+ DateFormat df = new SimpleDateFormat(MarketoUtils.MARKETO_DATE_SIMPLE_DATE_FORMAT);
167
+ assertEquals(df.format(toDate), configDiff.get(String.class, "from_date"));
168
+ }
169
+ }
@@ -0,0 +1,343 @@
1
+ package org.embulk.input.marketo.delegate;
2
+
3
+ import com.fasterxml.jackson.core.JsonParseException;
4
+ import com.fasterxml.jackson.databind.JavaType;
5
+ import com.fasterxml.jackson.databind.JsonMappingException;
6
+ import com.fasterxml.jackson.databind.ObjectMapper;
7
+ import com.fasterxml.jackson.databind.node.ObjectNode;
8
+ import com.google.common.base.Optional;
9
+ import com.google.common.base.Predicate;
10
+ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
11
+ import org.embulk.EmbulkTestRuntime;
12
+ import org.embulk.base.restclient.ServiceResponseMapper;
13
+ import org.embulk.base.restclient.record.RecordImporter;
14
+ import org.embulk.base.restclient.record.ValueLocator;
15
+ import org.embulk.config.ConfigDiff;
16
+ import org.embulk.config.ConfigException;
17
+ import org.embulk.config.ConfigLoader;
18
+ import org.embulk.config.ConfigSource;
19
+ import org.embulk.config.TaskReport;
20
+ import org.embulk.input.marketo.MarketoUtils;
21
+ import org.embulk.input.marketo.delegate.ProgramInputPlugin.PluginTask;
22
+ import org.embulk.input.marketo.delegate.ProgramInputPlugin.QueryBy;
23
+ import org.embulk.input.marketo.rest.MarketoRestClient;
24
+ import org.embulk.input.marketo.rest.RecordPagingIterable;
25
+ import org.embulk.spi.PageBuilder;
26
+ import org.embulk.spi.Schema;
27
+ import org.joda.time.DateTime;
28
+ import org.joda.time.Duration;
29
+ import org.joda.time.format.DateTimeFormat;
30
+ import org.joda.time.format.DateTimeFormatter;
31
+ import org.junit.Before;
32
+ import org.junit.Rule;
33
+ import org.junit.Test;
34
+ import org.junit.rules.ExpectedException;
35
+ import org.junit.rules.RuleChain;
36
+ import org.junit.rules.TestRule;
37
+ import org.mockito.ArgumentCaptor;
38
+ import org.mockito.Mockito;
39
+
40
+ import java.io.IOException;
41
+ import java.util.Arrays;
42
+ import java.util.Date;
43
+ import java.util.List;
44
+
45
+ import static org.junit.Assert.assertArrayEquals;
46
+ import static org.junit.Assert.assertEquals;
47
+
48
+ public class ProgramInputPluginTest
49
+ {
50
+ private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern(MarketoUtils.MARKETO_DATE_SIMPLE_DATE_FORMAT);
51
+
52
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
53
+
54
+ @Rule
55
+ public EmbulkTestRuntime runtime = new EmbulkTestRuntime();
56
+
57
+ private ExpectedException thrown = ExpectedException.none();
58
+
59
+ @Rule
60
+ @SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
61
+ public TestRule chain = RuleChain.outerRule(runtime).around(thrown);
62
+
63
+ private ConfigSource baseConfig;
64
+
65
+ private ProgramInputPlugin mockPlugin;
66
+
67
+ private MarketoRestClient mockRestClient;
68
+
69
+ @Before
70
+ public void setUp() throws Exception
71
+ {
72
+ mockPlugin = Mockito.spy(new ProgramInputPlugin());
73
+ baseConfig = config();
74
+ mockRestClient = Mockito.mock(MarketoRestClient.class);
75
+ Mockito.doReturn(mockRestClient).when(mockPlugin).createMarketoRestClient(Mockito.any(ProgramInputPlugin.PluginTask.class));
76
+ }
77
+
78
+ // -----------Verify configs --------------
79
+ @Test
80
+ public void testQueryByTagTypeConfigMissingTagType()
81
+ {
82
+ thrown.expect(ConfigException.class);
83
+ thrown.expectMessage("tag_type and tag_value are required when query by Tag Type");
84
+ ConfigSource config = baseConfig.set("query_by", Optional.of(QueryBy.TAG_TYPE)).set("tag_value", Optional.of("dummy"));
85
+ mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
86
+ }
87
+
88
+ @Test
89
+ public void testQueryByTagTypeConfigMissingTagValue()
90
+ {
91
+ thrown.expect(ConfigException.class);
92
+ thrown.expectMessage("tag_type and tag_value are required when query by Tag Type");
93
+ ConfigSource config = baseConfig.set("query_by", Optional.of(QueryBy.TAG_TYPE)).set("tag_type", Optional.of("dummy"));
94
+ mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
95
+ }
96
+
97
+ @Test
98
+ public void testQueryByDateRangeConfigMissingEarliestUpatedAt()
99
+ {
100
+ thrown.expect(ConfigException.class);
101
+ thrown.expectMessage("`earliest_updated_at` is required when query by Date Range");
102
+ ConfigSource config = baseConfig.set("query_by", Optional.of(QueryBy.DATE_RANGE)).set("latest_updated_at", Optional.of(DateTime.now().minusDays(10).toDate()));
103
+ mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
104
+ }
105
+
106
+ @Test
107
+ public void testQueryByDateRangeConfigMissingLatestUpdatedAt()
108
+ {
109
+ thrown.expect(ConfigException.class);
110
+ thrown.expectMessage("`latest_updated_at` is required when query by Date Range");
111
+ ConfigSource config = baseConfig.set("query_by", Optional.of(QueryBy.DATE_RANGE)).set("earliest_updated_at", Optional.of(DateTime.now().minusDays(10).toDate()));
112
+ mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
113
+ }
114
+
115
+ @Test
116
+ public void testQueryByDateRangeConfigMissingLatestUpdatedAtNonIncremental()
117
+ {
118
+ thrown.expect(ConfigException.class);
119
+ thrown.expectMessage("`latest_updated_at` is required when query by Date Range");
120
+ ConfigSource config = baseConfig
121
+ .set("query_by", Optional.of(QueryBy.DATE_RANGE))
122
+ .set("incremental", Boolean.FALSE)
123
+ .set("earliest_updated_at", Optional.of(DateTime.now().minusDays(10).toDate()));
124
+ mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
125
+ }
126
+
127
+ @Test
128
+ public void testNoErrorQueryByDateRangeConfigHasReportDurationNonIncremental()
129
+ {
130
+ ConfigSource config = baseConfig
131
+ .set("query_by", Optional.of(QueryBy.DATE_RANGE))
132
+ .set("report_duration", Optional.of(60L * 1000))
133
+ .set("incremental", Boolean.FALSE)
134
+ .set("earliest_updated_at", Optional.of(DateTime.now().minusDays(10).toDate()));
135
+ mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
136
+ }
137
+
138
+ @Test
139
+ public void testNoErrorQueryByDateRangeConfigHasReportDuration()
140
+ {
141
+ ConfigSource config = baseConfig
142
+ .set("report_duration", Optional.of(60L * 1000))
143
+ .set("query_by", Optional.of(QueryBy.DATE_RANGE))
144
+ .set("earliest_updated_at", Optional.of(DateTime.now().minusDays(10).toDate()));
145
+ mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
146
+ }
147
+
148
+ @Test
149
+ public void testQueryByDateRangeConfigHasEarliestUpdatedAtExceededNow()
150
+ {
151
+ DateTime earliestUpdatedAt = DateTime.now().plusDays(1);
152
+ DateTime latestUpdatedAt = DateTime.now().minusDays(2);
153
+ thrown.expect(ConfigException.class);
154
+ thrown.expectMessage(String.format("`earliest_updated_at` (%s) cannot precede the current date ", earliestUpdatedAt.toString(DATE_FORMATTER)));
155
+ ConfigSource config = baseConfig
156
+ .set("query_by", Optional.of(QueryBy.DATE_RANGE))
157
+ .set("earliest_updated_at", Optional.of(earliestUpdatedAt))
158
+ .set("latest_updated_at", Optional.of(latestUpdatedAt));
159
+ mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
160
+ }
161
+
162
+ @Test
163
+ public void testQueryByDateRangeConfigHasEarliestUpdatedAtExceededLatestUpdatedAt()
164
+ {
165
+ DateTime earliestUpdatedAt = DateTime.now().minusDays(10);
166
+ DateTime latestUpdatedAt = DateTime.now().minusDays(20);
167
+ thrown.expect(ConfigException.class);
168
+ thrown.expectMessage(String.format("Invalid date range. `earliest_updated_at` (%s) cannot precede the `latest_updated_at` (%s).",
169
+ earliestUpdatedAt.toString(DATE_FORMATTER),
170
+ latestUpdatedAt.toString(DATE_FORMATTER)));
171
+
172
+ ConfigSource config = baseConfig
173
+ .set("query_by", Optional.of(QueryBy.DATE_RANGE))
174
+ .set("earliest_updated_at", Optional.of(earliestUpdatedAt))
175
+ .set("latest_updated_at", Optional.of(latestUpdatedAt));
176
+ mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
177
+ }
178
+
179
+ @Test
180
+ public void testHasFilterTypeButMissingFilterValue()
181
+ {
182
+ DateTime earliestUpdatedAt = DateTime.now().minusDays(20);
183
+ DateTime latestUpdatedAt = DateTime.now().minusDays(10);
184
+ thrown.expect(ConfigException.class);
185
+ thrown.expectMessage("filter_value is required when selected filter_type");
186
+
187
+ ConfigSource config = baseConfig
188
+ .set("query_by", Optional.of(QueryBy.DATE_RANGE))
189
+ .set("earliest_updated_at", Optional.of(earliestUpdatedAt))
190
+ .set("latest_updated_at", Optional.of(latestUpdatedAt))
191
+ .set("filter_type", Optional.of("dummy"));
192
+ mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
193
+ }
194
+
195
+ @Test
196
+ public void testSkipIncrementalRunIfLastUpdatedAtExceedsNow()
197
+ {
198
+ DateTime earliestUpdatedAt = DateTime.now().minusDays(20);
199
+ DateTime latestUpdatedAt = earliestUpdatedAt.plusDays(21);
200
+ //21 days
201
+ long reportDuration = 21 * 24 * 60 * 60 * 1000;
202
+
203
+ ConfigSource config = baseConfig
204
+ .set("query_by", Optional.of(QueryBy.DATE_RANGE))
205
+ .set("earliest_updated_at", Optional.of(earliestUpdatedAt))
206
+ .set("latest_updated_at", Optional.of(latestUpdatedAt))
207
+ .set("report_duration", reportDuration)
208
+ .set("incremental", true);
209
+ ServiceResponseMapper<? extends ValueLocator> mapper = mockPlugin.buildServiceResponseMapper(config.loadConfig(PluginTask.class));
210
+ RecordImporter recordImporter = mapper.createRecordImporter();
211
+ PageBuilder mockPageBuilder = Mockito.mock(PageBuilder.class);
212
+ TaskReport taskReport = mockPlugin.ingestServiceData(config.loadConfig(PluginTask.class), recordImporter, 1, mockPageBuilder);
213
+ // page builder object should never get called.
214
+ Mockito.verify(mockPageBuilder, Mockito.never()).addRecord();
215
+
216
+ String earliestUpdatedAtStr = taskReport.get(String.class, "earliest_updated_at");
217
+ long duration = taskReport.get(Long.class, "report_duration");
218
+ assertEquals(duration, reportDuration);
219
+ assertEquals(earliestUpdatedAtStr, earliestUpdatedAt.toString(DATE_FORMATTER));
220
+ }
221
+
222
+ @Test
223
+ public void testBuildConfigDiff() throws Exception
224
+ {
225
+ TaskReport taskReport1 = Mockito.mock(TaskReport.class);
226
+ DateTime earliestUpdatedAt = DateTime.now().minusDays(20);
227
+ DateTime latestUpdatedAt = DateTime.now().minusDays(10);
228
+ ConfigSource config = baseConfig
229
+ .set("query_by", Optional.of(QueryBy.DATE_RANGE))
230
+ .set("earliest_updated_at", Optional.of(earliestUpdatedAt))
231
+ .set("latest_updated_at", Optional.of(latestUpdatedAt))
232
+ .set("incremental", true);
233
+
234
+ ConfigDiff diff = mockPlugin.buildConfigDiff(config.loadConfig(PluginTask.class), Mockito.mock(Schema.class), 1, Arrays.asList(taskReport1));
235
+
236
+ long reportDuration = diff.get(Long.class, "report_duration");
237
+ String nextErliestUpdatedAt = diff.get(String.class, "earliest_updated_at");
238
+
239
+ assertEquals(reportDuration, new Duration(earliestUpdatedAt, latestUpdatedAt).getMillis());
240
+ assertEquals(nextErliestUpdatedAt, latestUpdatedAt.plusSeconds(1).toString(DATE_FORMATTER));
241
+ }
242
+
243
+ @SuppressWarnings("unchecked")
244
+ private void testRun(ConfigSource config, Predicate<MarketoRestClient> expectedCall) throws JsonParseException, JsonMappingException, IOException
245
+ {
246
+ // Mock response data
247
+ RecordPagingIterable<ObjectNode> mockRecordPagingIterable = Mockito.mock(RecordPagingIterable.class);
248
+ JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametrizedType(List.class, List.class, ObjectNode.class);
249
+ List<ObjectNode> objectNodeList = OBJECT_MAPPER.readValue(this.getClass().getResourceAsStream("/fixtures/all_program_full.json"), javaType);
250
+ Mockito.when(mockRecordPagingIterable.iterator()).thenReturn(objectNodeList.iterator());
251
+ Mockito.when(mockRestClient.getProgramsByTag(Mockito.anyString(), Mockito.anyString())).thenReturn(mockRecordPagingIterable);
252
+ Mockito.when(mockRestClient.getPrograms()).thenReturn(mockRecordPagingIterable);
253
+ Mockito.when(mockRestClient.getProgramsByDateRange(
254
+ Mockito.any(Date.class),
255
+ Mockito.any(Date.class),
256
+ Mockito.nullable(String.class),
257
+ Mockito.nullable(List.class))).thenReturn(mockRecordPagingIterable);
258
+
259
+ ServiceResponseMapper<? extends ValueLocator> mapper = mockPlugin.buildServiceResponseMapper(config.loadConfig(PluginTask.class));
260
+ RecordImporter recordImporter = mapper.createRecordImporter();
261
+ PageBuilder mockPageBuilder = Mockito.mock(PageBuilder.class);
262
+ mockPlugin.ingestServiceData(config.loadConfig(PluginTask.class), recordImporter, 1, mockPageBuilder);
263
+
264
+ // The method getProgramByTag is called 1 time
265
+ // Mockito.verify(mockRestClient, Mockito.times(1)).getProgramsByTag(Mockito.anyString(), Mockito.anyString());
266
+ expectedCall.apply(mockRestClient);
267
+
268
+ Schema embulkSchema = mapper.getEmbulkSchema();
269
+ // 17 columns
270
+ assertEquals(embulkSchema.size(), 17);
271
+ // verify 3 times the method setLong for column id has been called
272
+ ArgumentCaptor<Long> longArgumentCaptor = ArgumentCaptor.forClass(Long.class);
273
+ Mockito.verify(mockPageBuilder, Mockito.times(3)).setLong(Mockito.eq(embulkSchema.lookupColumn("id")), longArgumentCaptor.capture());
274
+ List<Long> allValues = longArgumentCaptor.getAllValues();
275
+ assertArrayEquals(new Long[]{1004L, 1001L, 1003L}, allValues.toArray());
276
+ }
277
+
278
+ @Test
279
+ public void testRunQueryByTagType() throws JsonParseException, JsonMappingException, IOException
280
+ {
281
+ ConfigSource config = baseConfig
282
+ .set("query_by", Optional.of(QueryBy.TAG_TYPE))
283
+ .set("tag_type", Optional.of("dummy"))
284
+ .set("tag_value", Optional.of("dummy"));
285
+ Predicate<MarketoRestClient> expectedCall = new Predicate<MarketoRestClient>()
286
+ {
287
+ @Override
288
+ public boolean apply(MarketoRestClient mockRest)
289
+ {
290
+ Mockito.verify(mockRest, Mockito.times(1)).getProgramsByTag(Mockito.anyString(), Mockito.anyString());
291
+ return true;
292
+ }
293
+ };
294
+ testRun(config, expectedCall);
295
+ }
296
+
297
+ @Test
298
+ public void testRunWithoutQueryBy() throws JsonParseException, JsonMappingException, IOException
299
+ {
300
+ Predicate<MarketoRestClient> expectedCall = new Predicate<MarketoRestClient>()
301
+ {
302
+ @Override
303
+ public boolean apply(MarketoRestClient input)
304
+ {
305
+ Mockito.verify(input, Mockito.times(1)).getPrograms();
306
+ return true;
307
+ }
308
+ };
309
+ testRun(baseConfig, expectedCall);
310
+ }
311
+
312
+ @Test
313
+ public void testRunQueryByDateRange() throws JsonParseException, JsonMappingException, IOException
314
+ {
315
+ DateTime earliestUpdatedAt = DateTime.now().minusDays(20);
316
+ DateTime latestUpdatedAt = DateTime.now().minusDays(10);
317
+ ConfigSource config = baseConfig
318
+ .set("query_by", Optional.of(QueryBy.DATE_RANGE))
319
+ .set("earliest_updated_at", Optional.of(earliestUpdatedAt))
320
+ .set("latest_updated_at", Optional.of(latestUpdatedAt));
321
+ Predicate<MarketoRestClient> expectedCall = new Predicate<MarketoRestClient>()
322
+ {
323
+ @SuppressWarnings("unchecked")
324
+ @Override
325
+ public boolean apply(MarketoRestClient input)
326
+ {
327
+ Mockito.verify(input, Mockito.times(1)).getProgramsByDateRange(
328
+ Mockito.any(Date.class),
329
+ Mockito.any(Date.class),
330
+ Mockito.nullable(String.class),
331
+ Mockito.nullable(List.class));
332
+ return true;
333
+ }
334
+ };
335
+ testRun(config, expectedCall);
336
+ }
337
+
338
+ public ConfigSource config() throws IOException
339
+ {
340
+ ConfigLoader configLoader = runtime.getInjector().getInstance(ConfigLoader.class);
341
+ return configLoader.fromYaml(this.getClass().getResourceAsStream("/config/rest_config.yaml"));
342
+ }
343
+ }