001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.kuali.module.gl.batch;
017:
018: import java.io.BufferedReader;
019: import java.io.File;
020: import java.io.FileFilter;
021: import java.io.FileReader;
022: import java.io.IOException;
023: import java.util.ArrayList;
024: import java.util.Collection;
025: import java.util.Collections;
026: import java.util.HashSet;
027: import java.util.List;
028: import java.util.Set;
029:
030: import org.apache.commons.io.filefilter.AndFileFilter;
031: import org.apache.commons.io.filefilter.PrefixFileFilter;
032: import org.apache.commons.io.filefilter.SuffixFileFilter;
033: import org.apache.commons.lang.StringUtils;
034: import org.kuali.core.service.KualiConfigurationService;
035: import org.kuali.kfs.KFSConstants;
036: import org.kuali.kfs.context.SpringContext;
037: import org.kuali.module.gl.OriginEntryTestBase;
038: import org.kuali.module.gl.bo.OriginEntryFull;
039: import org.kuali.module.gl.bo.OriginEntryGroup;
040: import org.kuali.module.gl.bo.OriginEntrySource;
041: import org.kuali.module.gl.service.impl.FileEnterpriseFeederServiceImpl;
042: import org.kuali.test.ConfigureContext;
043:
044: /**
045: * This class tests the enterprise feeder service. The concept of a file set is pervasive throughout this test. A file set consists
046: * of:
047: * <ul>
048: * <li>an ID value, starting at 1. The next file set is identified with 2, and so on. NOTE: when the ID value is used in a filename
049: * (see below), it is left-padded with zero's to make it 3 characters long (e.g. 001, 002, 010, 865). </li>
050: * <li>a data file, this file will be named entp_test_file_<id value>.data</li>
051: * <li>a reconciliation file, this file will be named entp_test_file<id value>.recon . </li>
052: * </ul>
053: * The IDs are left-padded so that they can be properly sorted by file name, so that files may be fed in a predictable order. For
054: * each particular test case in the code, a list of file sets IDs are selected to be tested. The done files will be generated for
055: * each of the selected file set IDs before the feeder is invoked, and they should hopefully be deleted by the feeder. Dependencies:
056: * <ul>
057: * <li>there are file set in the enterprise feeder staging directory (injected via Spring) that are necessary for the feeder to be
058: * tested correctly</li>
059: * <li>the staging directory may contain data and reconciliation files not related to this test, but they may NOT include done
060: * files. Having done files will result in TEST FAILURE</li>
061: * <li> if any file sets are added and/or modified, then the method {@link FileEnterpriseFeederTest#isFilesetValid(int)} must be
062: * modified, if necessary.</li>
063: * <li>The files for all file sets must be in the directory configured for use by the enterprise feeder step bean. Running the
064: * dist-local target in build.xml should place the files in the external configuration directory, or they can be copied from
065: * build/externalConfigDirectory/static/staging/GL . See the entp_test_file_readme.txt file for more information about the file
066: * sets.</li>
067: * </ul>
068: */
069: @ConfigureContext
070: public class FileEnterpriseFeederTest extends OriginEntryTestBase {
071: // to be populated in setUp
072: private List<String> prerequisiteDataFiles;
073: private List<String> prerequisiteReconFiles;
074:
075: public static final String TEST_FILE_PREFIX = "entp_test_file_";
076: public static final int NUM_TEST_FILE_SETS = 4;
077: public static final int ORIGIN_ENTRY_TEXT_LINE_LENGTH = 173;
078:
079: /**
080: * Sets up the proper file names needed for the test.
081: * @see junit.framework.TestCase#setUp()
082: */
083: @Override
084: protected void setUp() throws Exception {
085: super .setUp();
086:
087: prerequisiteDataFiles = new ArrayList<String>();
088: prerequisiteReconFiles = new ArrayList<String>();
089:
090: for (int i = 1; i <= NUM_TEST_FILE_SETS; i++) {
091: prerequisiteDataFiles.add(generateDataFilename(i));
092: prerequisiteReconFiles.add(generateReconFilename(i));
093: }
094:
095: checkNecessaryFilesPresentAndReadable();
096: checkNotOnProduction();
097: }
098:
099: /**
100: * Tests to ensure that the feeder will not feed upon anything if no done files exist.
101: *
102: * @throws Exception thrown if some vague thing goes wrong
103: */
104: // @RelatesTo(RelatesTo.JiraIssue.KULUT30)
105: public final void testNoDoneFiles() throws Exception {
106: List<Integer> fileSets = Collections.emptyList();
107:
108: initializeDatabaseForTest();
109:
110: assertNoExtraDoneFilesExistAndCreateDoneFilesForSets(fileSets);
111:
112: EnterpriseFeedStep feederStep = SpringContext
113: .getBean(EnterpriseFeedStep.class);
114: feederStep.execute(getClass().getName());
115:
116: assertDoneFilesDeleted(fileSets);
117:
118: OriginEntryGroup group = getGroupCreatedByFeed();
119: System.out.println(group);
120: int groupCount = originEntryService
121: .getGroupCount(group.getId());
122: assertTrue("Expected group count of 0, but got group count of "
123: + groupCount, groupCount == 0);
124:
125: assertNoExtraTestDoneFilesExistAfterTest();
126: }
127:
128: /**
129: * This method tests that the uploading of a single OK file set
130: *
131: * @throws Exception thrown if some vague thing goes wrong
132: */
133: // @RelatesTo(RelatesTo.JiraIssue.KULUT30)
134: public final void testOneOkFileSet() throws Exception {
135: List<Integer> fileSets = new ArrayList<Integer>();
136: fileSets.add(2);
137:
138: initializeDatabaseForTest();
139: assertNoExtraDoneFilesExistAndCreateDoneFilesForSets(fileSets);
140:
141: EnterpriseFeedStep feederStep = SpringContext
142: .getBean(EnterpriseFeedStep.class);
143: assertTrue("Step should have returned true", feederStep
144: .execute(getClass().getName()));
145:
146: assertDoneFilesDeleted(fileSets);
147:
148: OriginEntryGroup group = getGroupCreatedByFeed();
149: System.out.println(group);
150: System.out.println(originEntryService.getGroupCount(group
151: .getId()));
152:
153: List<String> expectedEntries = buildVerificationEntries(
154: fileSets, group);
155: assertOriginEntriesLoaded(expectedEntries, group);
156:
157: assertNoExtraTestDoneFilesExistAfterTest();
158: }
159:
160: /**
161: * Tests the uploading of two files, one parsable, the other not
162: *
163: * @throws Exception thrown if anything goes wrong
164: */
165: // @RelatesTo(RelatesTo.JiraIssue.KULUT30)
166: public final void testOneOkOneBadFileSet() throws Exception {
167: List<Integer> fileSets = new ArrayList<Integer>();
168: fileSets.add(1);
169: fileSets.add(2);
170:
171: initializeDatabaseForTest();
172: assertNoExtraDoneFilesExistAndCreateDoneFilesForSets(fileSets);
173:
174: EnterpriseFeedStep feederStep = SpringContext
175: .getBean(EnterpriseFeedStep.class);
176: assertTrue("Step should have returned true", feederStep
177: .execute(getClass().getName()));
178:
179: assertDoneFilesDeleted(fileSets);
180:
181: OriginEntryGroup group = getGroupCreatedByFeed();
182: System.out.println(group);
183: System.out.println(originEntryService.getGroupCount(group
184: .getId()));
185:
186: List<String> expectedEntries = buildVerificationEntries(
187: fileSets, group);
188: assertOriginEntriesLoaded(expectedEntries, group);
189:
190: assertNoExtraTestDoneFilesExistAfterTest();
191: }
192:
193: /**
194: * Tests that the enterprise feeder will successfully run, even when fed a bad reconciliation file
195: *
196: * @throws Exception
197: */
198: // @RelatesTo(RelatesTo.JiraIssue.KULUT30)
199: public final void testBadReconFileSet() throws Exception {
200: List<Integer> fileSets = new ArrayList<Integer>();
201: fileSets.add(2);
202: fileSets.add(3);
203:
204: initializeDatabaseForTest();
205: assertNoExtraDoneFilesExistAndCreateDoneFilesForSets(fileSets);
206:
207: EnterpriseFeedStep feederStep = SpringContext
208: .getBean(EnterpriseFeedStep.class);
209: assertTrue("Step should have returned true", feederStep
210: .execute(getClass().getName()));
211:
212: assertDoneFilesDeleted(fileSets);
213:
214: OriginEntryGroup group = getGroupCreatedByFeed();
215: System.out.println(group);
216: System.out.println(originEntryService.getGroupCount(group
217: .getId()));
218:
219: List<String> expectedEntries = buildVerificationEntries(
220: fileSets, group);
221: assertOriginEntriesLoaded(expectedEntries, group);
222:
223: assertNoExtraTestDoneFilesExistAfterTest();
224: }
225:
226: /**
227: * Tests that the enterprise feeder will successfully run, even when there's a missing data file
228: *
229: * @throws Exception thrown if anything goes wrong
230: */
231: // @RelatesTo(RelatesTo.JiraIssue.KULUT30)
232: public final void testDataFileMissing() throws Exception {
233: List<Integer> fileSets = new ArrayList<Integer>();
234: fileSets.add(2);
235:
236: initializeDatabaseForTest();
237: assertNoExtraDoneFilesExistAndCreateDoneFilesForSets(fileSets);
238:
239: EnterpriseFeedStep feederStep = SpringContext
240: .getBean(EnterpriseFeedStep.class);
241: assertTrue("Step should have returned true", feederStep
242: .execute(getClass().getName()));
243:
244: assertDoneFilesDeleted(fileSets);
245:
246: OriginEntryGroup group = getGroupCreatedByFeed();
247: System.out.println(group);
248: System.out.println(originEntryService.getGroupCount(group
249: .getId()));
250:
251: List<String> expectedEntries = buildVerificationEntries(
252: fileSets, group);
253: assertOriginEntriesLoaded(expectedEntries, group);
254:
255: assertNoExtraTestDoneFilesExistAfterTest();
256: }
257:
258: /**
259: * Clears out the origin entry and origin entry group tables to prepare for the test
260: */
261: protected void initializeDatabaseForTest() {
262: clearOriginEntryTables();
263: }
264:
265: /**
266: * Returns the origin entry group created by the enterprise feed process and does some
267: * basic assertions against it.
268: *
269: * @return the OriginEntryGroup created by the enterprise feed process
270: */
271: protected OriginEntryGroup getGroupCreatedByFeed() {
272: Collection<OriginEntryGroup> groups = originEntryGroupService
273: .getAllOriginEntryGroup();
274: assertEquals(
275: "Either the initializeDatabaseFOrTest method was not called before "
276: + "running the test, or more than one group was created by the feeder service.",
277: groups.size(), 1);
278: OriginEntryGroup group = groups.iterator().next();
279: assertEquals(
280: "Unexpected origin entry group source code: expected: "
281: + OriginEntrySource.ENTERPRISE_FEED
282: + " actual: " + group.getSourceCode(),
283: OriginEntrySource.ENTERPRISE_FEED, group
284: .getSourceCode());
285:
286: assertTrue("Valid flag of group should be true.", group
287: .getValid().booleanValue());
288: assertTrue("Process flag of group should be true.", group
289: .getProcess().booleanValue());
290: assertTrue("Scrub flag of group should be true.", group
291: .getScrub().booleanValue());
292:
293: return group;
294: }
295:
296: /**
297: * Makes sure that the data files for this test exist; if not, throws an exception
298: */
299: // @RelatesTo(RelatesTo.JiraIssue.KULUT30)
300: protected void checkNecessaryFilesPresentAndReadable() {
301: boolean invalidFiles = false;
302: StringBuilder problemFiles = new StringBuilder();
303:
304: for (int i = 0; i < prerequisiteDataFiles.size(); i++) {
305: File file = new File(prerequisiteDataFiles.get(i));
306: if (!file.exists() || !file.canRead()) {
307: problemFiles.append(prerequisiteDataFiles.get(i))
308: .append("; ");
309: }
310: }
311: for (int i = 0; i < prerequisiteReconFiles.size(); i++) {
312: File file = new File(prerequisiteReconFiles.get(i));
313: if (!file.exists() || !file.canRead()) {
314: problemFiles.append(prerequisiteReconFiles.get(i))
315: .append("; ");
316: }
317: }
318:
319: if (problemFiles.length() != 0) {
320: throw new RuntimeException(
321: "The following files required for testing are either missing or not readable: "
322: + problemFiles.toString());
323: }
324: }
325:
326: /**
327: * Zero pads an integer to be at least 3 digits long
328: *
329: * @param value an integer value
330: * @return a left zero padded String
331: */
332: protected String convertIntToString(int value) {
333: if (value < 10) {
334: return "00" + Integer.toString(value);
335: }
336: if (value < 100) {
337: return "0" + Integer.toString(value);
338: }
339: return Integer.toString(value);
340: }
341:
342: /**
343: * Generates the full path and file name of a generated enterprise feed data file
344: *
345: * @param fileSetId the integer id of the file that should have been generated
346: * @return the full path and file name for the file
347: */
348: protected String generateDataFilename(int fileSetId) {
349: String directoryPrefix = ((FileEnterpriseFeederServiceImpl) SpringContext
350: .getBean(FileEnterpriseFeederServiceImpl.class))
351: .getDirectoryName()
352: + File.separator;
353: return directoryPrefix + TEST_FILE_PREFIX
354: + convertIntToString(fileSetId)
355: + FileEnterpriseFeederServiceImpl.DATA_FILE_SUFFIX;
356: }
357:
358: /**
359: * Generates the full path and file name of a generated enterprise feed reconciliation file
360: *
361: * @param fileSetId the integer id of the file that should have been generated
362: * @return the full path and file name for the file
363: */
364: protected String generateReconFilename(int fileSetId) {
365: String directoryPrefix = ((FileEnterpriseFeederServiceImpl) SpringContext
366: .getBean(FileEnterpriseFeederServiceImpl.class))
367: .getDirectoryName()
368: + File.separator;
369: return directoryPrefix + TEST_FILE_PREFIX
370: + convertIntToString(fileSetId)
371: + FileEnterpriseFeederServiceImpl.RECON_FILE_SUFFIX;
372: }
373:
374: /**
375: * Generates the full path and file name of a generated enterprise feed .done file
376: *
377: * @param fileSetId the integer id of the file that should have been generated
378: * @return the full path and file name for the file
379: */
380: protected String generateDoneFilename(int fileSetId) {
381: String directoryPrefix = ((FileEnterpriseFeederServiceImpl) SpringContext
382: .getBean(FileEnterpriseFeederServiceImpl.class))
383: .getDirectoryName()
384: + File.separator;
385: return directoryPrefix + TEST_FILE_PREFIX
386: + convertIntToString(fileSetId)
387: + FileEnterpriseFeederServiceImpl.DONE_FILE_SUFFIX;
388: }
389:
390: /**
391: * This method asserts that there doesn't exist any done files in the enterprise feeder directory that do not begin with
392: * DONE_FILE_PREFIX (see constants definition in this class). If there are files that begin w/ that prefix in the directory,
393: * they are deleted. After checking/ deleting done files, it will then create a done files listed in the fileSets parameter.
394: *
395: * @param fileSets A list of Integers, representing the done files that will be created. (see class description) to see how
396: * these integers map into file names.
397: * @throws IOException if a file cannot be successfully read
398: */
399: protected void assertNoExtraDoneFilesExistAndCreateDoneFilesForSets(
400: List<Integer> fileSets) throws IOException {
401: FileFilter fileFilter = new SuffixFileFilter(
402: FileEnterpriseFeederServiceImpl.DONE_FILE_SUFFIX);
403: File directory = new File(
404: ((FileEnterpriseFeederServiceImpl) SpringContext
405: .getBean(FileEnterpriseFeederServiceImpl.class))
406: .getDirectoryName());
407: File[] doneFiles = directory.listFiles(fileFilter);
408:
409: StringBuilder sb = new StringBuilder();
410: for (File file : doneFiles) {
411: if (file.getName().startsWith(TEST_FILE_PREFIX)) {
412: // this is a test done file, just delete it
413: file.delete();
414: } else {
415: // maybe someone put in files in the staging directory that shouldn't be there
416: sb.append(file.getName() + ";");
417: }
418: }
419:
420: assertTrue(
421: "Done files exist in the directory ( "
422: + sb.toString()
423: + " ), which will cause this step to produce unexpected results when testing."
424: + " To run this test, the done files must be removed (do NOT do this if running on a production system).",
425: doneFiles.length == 0);
426:
427: for (Integer setNumber : fileSets) {
428: File doneFile = new File(generateDoneFilename(setNumber));
429: if (!doneFile.exists()) {
430: doneFile.createNewFile();
431: }
432: }
433: }
434:
435: /**
436: * Asserts true if no test done files exist. If so, method removes done files before assert.
437: *
438: * @throws IOException thrown if a data file cannot be successfully read
439: */
440: protected void assertNoExtraTestDoneFilesExistAfterTest()
441: throws IOException {
442: FileFilter fileFilter = new AndFileFilter(new PrefixFileFilter(
443: TEST_FILE_PREFIX), new SuffixFileFilter(
444: FileEnterpriseFeederServiceImpl.DONE_FILE_SUFFIX));
445: File directory = new File(
446: ((FileEnterpriseFeederServiceImpl) SpringContext
447: .getBean(FileEnterpriseFeederServiceImpl.class))
448: .getDirectoryName());
449: File[] doneFiles = directory.listFiles(fileFilter);
450:
451: StringBuilder buf = new StringBuilder();
452: for (File file : doneFiles) {
453: buf.append(file.getName() + ";");
454: file.delete();
455: }
456:
457: assertTrue(
458: "The following test done files existed ( "
459: + buf.toString()
460: + " ), but shouldn't have. These test done files have been deleted. ",
461: buf.length() == 0);
462: }
463:
464: /**
465: * Asserts that there are no longer any existing .done files
466: *
467: * @param fileSets a List of file sets to check .done files for
468: * @throws IOException thrown if one of the files cannot be read for any reason
469: */
470: protected void assertDoneFilesDeleted(List<Integer> fileSets)
471: throws IOException {
472: StringBuilder buf = new StringBuilder();
473:
474: for (Integer setNumber : fileSets) {
475: File doneFile = new File(generateDoneFilename(setNumber));
476: if (doneFile.exists()) {
477: buf.append(doneFile.getAbsolutePath()).append("; ");
478: }
479: }
480:
481: assertTrue("The following done files were not deleted: "
482: + buf.toString(), buf.length() == 0);
483: }
484:
485: /**
486: * Converts the entries generated by the enterprise feed to String-formatted entries
487: *
488: * @param fileSets the file sets to convert entries for
489: * @param group not used as such
490: * @return a List of String-formatted generated origin entries
491: * @throws IOException thrown if one of the data files cannot be read successfully
492: */
493: protected List<String> buildVerificationEntries(
494: List<Integer> fileSets, OriginEntryGroup group)
495: throws IOException {
496: List<String> entries = new ArrayList<String>();
497:
498: for (Integer fileSetId : fileSets) {
499: if (isFilesetLoadable(fileSetId)) {
500: File file = new File(generateDataFilename(fileSetId));
501: BufferedReader buf = new BufferedReader(new FileReader(
502: file));
503:
504: String line;
505: while ((line = buf.readLine()) != null) {
506: entries.add(line.substring(0,
507: ORIGIN_ENTRY_TEXT_LINE_LENGTH));
508: }
509: }
510: }
511: return entries;
512: }
513:
514: /**
515: * Fails if the origin entries in the list do not match the origin entries associated with the passed in group.
516: *
517: * @param expectedEntries the entries that were the expected output of the enterprise feed process
518: * @param groupOfLoadedEntries the entries that were really the output of the enterprise feed process
519: */
520: protected void assertOriginEntriesLoaded(
521: List<String> expectedEntries,
522: OriginEntryGroup groupOfLoadedEntries) {
523: Collection<OriginEntryFull> actualEntries = originEntryDao
524: .testingGetAllEntries();
525:
526: assertEquals(
527: "Expected and actual number of loaded origin entries do not match.",
528: expectedEntries.size(), actualEntries.size());
529:
530: for (OriginEntryFull actualEntry : actualEntries) {
531: String line = actualEntry.getLine().substring(0,
532: ORIGIN_ENTRY_TEXT_LINE_LENGTH);
533: assertTrue(
534: "Unexpected line loaded into origin entry table: "
535: + line, expectedEntries.remove(line));
536: }
537:
538: if (!expectedEntries.isEmpty()) {
539: System.err
540: .println("The following expected entries were not loaded into the database: ");
541: for (String expectedEntry : expectedEntries) {
542: System.err.println(expectedEntry);
543: }
544: fail("Some expected entries were not loaded into the database. See System.err output for details.");
545: }
546: }
547:
548: /**
549: * Determines whether the files in a set are able to be loaded because of the lack of parse errors and the lack of
550: * reconciliation errors
551: *
552: * @param fileSetId the integer id of the file set
553: * @return true if it can be loaded, false otherwise
554: */
555: protected boolean isFilesetLoadable(int fileSetId) {
556: Set<Integer> loadableSets = new HashSet<Integer>();
557: loadableSets.add(2);
558:
559: return loadableSets.contains(fileSetId);
560: }
561:
562: /**
563: * Throws an exception if running on production
564: */
565: protected void checkNotOnProduction() {
566: KualiConfigurationService kualiConfigurationService = SpringContext
567: .getBean(KualiConfigurationService.class);
568:
569: if (StringUtils
570: .equals(
571: kualiConfigurationService
572: .getPropertyString(KFSConstants.PROD_ENVIRONMENT_CODE_KEY),
573: kualiConfigurationService
574: .getPropertyString(KFSConstants.ENVIRONMENT_KEY))) {
575: throw new RuntimeException("Can't run on production");
576: }
577: }
578: }
|