Advertisement
Guest User

BaseContextSensitiveTest

a guest
Jun 20th, 2020
51
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.25 KB | None | 0 0
  1. /**
  2. * This Source Code Form is subject to the terms of the Mozilla Public License,
  3. * v. 2.0. If a copy of the MPL was not distributed with this file, You can
  4. * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
  5. * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
  6. *
  7. * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
  8. * graphic logo is a trademark of OpenMRS Inc.
  9. */
  10. package org.openmrs.test;
  11.  
  12. import org.apache.commons.io.IOUtils;
  13. import org.apache.commons.lang3.ArrayUtils;
  14. import org.apache.commons.lang3.StringUtils;
  15. import org.dbunit.DatabaseUnitException;
  16. import org.dbunit.DatabaseUnitRuntimeException;
  17. import org.dbunit.database.DatabaseConfig;
  18. import org.dbunit.database.DatabaseConnection;
  19. import org.dbunit.database.IDatabaseConnection;
  20. import org.dbunit.dataset.DataSetException;
  21. import org.dbunit.dataset.DefaultDataSet;
  22. import org.dbunit.dataset.DefaultTable;
  23. import org.dbunit.dataset.IDataSet;
  24. import org.dbunit.dataset.ReplacementDataSet;
  25. import org.dbunit.dataset.stream.StreamingDataSet;
  26. import org.dbunit.dataset.xml.FlatXmlDataSet;
  27. import org.dbunit.dataset.xml.FlatXmlProducer;
  28. import org.dbunit.dataset.xml.XmlDataSet;
  29. import org.dbunit.ext.h2.H2DataTypeFactory;
  30. import org.dbunit.operation.DatabaseOperation;
  31. import org.hibernate.SessionFactory;
  32. import org.hibernate.cfg.Environment;
  33. import org.hibernate.dialect.H2Dialect;
  34. import org.junit.jupiter.api.AfterAll;
  35. import org.junit.jupiter.api.AfterEach;
  36. import org.junit.jupiter.api.BeforeEach;
  37. import org.mockito.InjectMocks;
  38. import org.mockito.Mock;
  39. import org.mockito.MockitoAnnotations;
  40. import org.openmrs.ConceptName;
  41. import org.openmrs.Drug;
  42. import org.openmrs.PatientIdentifier;
  43. import org.openmrs.PersonAttribute;
  44. import org.openmrs.PersonName;
  45. import org.openmrs.User;
  46. import org.openmrs.annotation.OpenmrsProfileExcludeFilter;
  47. import org.openmrs.api.context.Context;
  48. import org.openmrs.api.context.ContextAuthenticationException;
  49. import org.openmrs.api.context.ContextMockHelper;
  50. import org.openmrs.api.context.Credentials;
  51. import org.openmrs.api.context.UsernamePasswordCredentials;
  52. import org.openmrs.module.ModuleConstants;
  53. import org.openmrs.util.DatabaseUtil;
  54. import org.openmrs.util.OpenmrsClassLoader;
  55. import org.openmrs.util.OpenmrsConstants;
  56. import org.openmrs.util.OpenmrsUtil;
  57. import org.slf4j.Logger;
  58. import org.slf4j.LoggerFactory;
  59. import org.springframework.context.ApplicationContext;
  60. import org.springframework.test.annotation.Rollback;
  61. import org.springframework.test.context.ContextConfiguration;
  62. import org.springframework.test.context.TestExecutionListeners;
  63. import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
  64. import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
  65. import org.springframework.transaction.annotation.Transactional;
  66. import org.xml.sax.InputSource;
  67.  
  68. import javax.swing.*;
  69. import java.awt.*;
  70. import java.io.File;
  71. import java.io.FileInputStream;
  72. import java.io.FileNotFoundException;
  73. import java.io.IOException;
  74. import java.io.InputStream;
  75. import java.io.InputStreamReader;
  76. import java.io.Reader;
  77. import java.nio.charset.StandardCharsets;
  78. import java.sql.Connection;
  79. import java.sql.PreparedStatement;
  80. import java.sql.ResultSet;
  81. import java.sql.SQLException;
  82. import java.util.Collections;
  83. import java.util.HashMap;
  84. import java.util.List;
  85. import java.util.Map;
  86. import java.util.Properties;
  87. import java.util.Timer;
  88. import java.util.TimerTask;
  89.  
  90. import static org.junit.jupiter.api.Assumptions.assumeTrue;
  91.  
  92. /**
  93. * This is the base for spring/context tests. Tests that NEED to use calls to the Context class and
  94. * use Services and/or the database should extend this class. NOTE: Tests that do not need access to
  95. * spring enabled services do not need this class and extending this will only slow those test cases
  96. * down. (because spring is started before test cases are run). Normal test cases do not need to
  97. * extend anything
  98. */
  99. @ContextConfiguration(locations = { "classpath:applicationContext-service.xml", "classpath*:openmrs-servlet.xml",
  100. "classpath*:moduleApplicationContext.xml", "classpath*:TestingApplicationContext.xml" })
  101. @TestExecutionListeners( { TransactionalTestExecutionListener.class, SkipBaseSetupAnnotationExecutionListener.class,
  102. StartModuleExecutionListener.class })
  103. @Transactional
  104. @Rollback
  105. @SpringJUnitConfig
  106. public abstract class BaseContextSensitiveTest {
  107.  
  108. private static final Logger log = LoggerFactory.getLogger(BaseContextSensitiveTest.class);
  109.  
  110. /**
  111. * Only the classpath/package path and filename of the initial dataset
  112. */
  113. protected static final String INITIAL_XML_DATASET_PACKAGE_PATH = "org/openmrs/include/initialInMemoryTestDataSet.xml";
  114.  
  115. protected static final String EXAMPLE_XML_DATASET_PACKAGE_PATH = "org/openmrs/include/standardTestDataset.xml";
  116.  
  117. /**
  118. * cached runtime properties
  119. */
  120. protected static Properties runtimeProperties;
  121.  
  122. protected static ApplicationContext applicationContext ;
  123.  
  124. /**
  125. * Used for username/password dialog
  126. */
  127. private static final Font font = new Font("Arial", Font.BOLD, 16);
  128.  
  129. /**
  130. * Our username field is outside of the getUsernameAndPassword() method so we can do our
  131. * force-focus-on-the-username-field trick -- i.e., refer to the field within an anonymous
  132. * TimerTask method.
  133. */
  134. private static JTextField usernameField;
  135.  
  136. /**
  137. * This frame contains the password dialog box. In order to bring the frame to the front in the
  138. * TimerTask method, we make it a private field
  139. */
  140. private static Frame frame;
  141.  
  142. /**
  143. * Static variable to keep track of the number of times this class has been loaded (aka, number
  144. * of tests already run)
  145. */
  146. private static Integer loadCount = 0;
  147.  
  148. /**
  149. * Allows to determine if the DB is initialized with standard data
  150. */
  151. private static boolean isBaseSetup;
  152.  
  153. /**
  154. * Stores a user authenticated for running tests which allows to discover a situation when some
  155. * test authenticates as a different user and we need to revert to the original one
  156. */
  157. private User authenticatedUser;
  158.  
  159. /**
  160. * Allows mocking services returned by Context. See {@link ContextMockHelper}
  161. *
  162. * @since 1.11, 1.10, 1.9.9
  163. */
  164. @InjectMocks
  165. protected ContextMockHelper contextMockHelper;
  166.  
  167. private static volatile BaseContextSensitiveTest instance;
  168.  
  169. /**
  170. * Basic constructor for the super class to all openmrs api unit tests. This constructor sets up
  171. * the classloader and the properties file so that by the type spring gets around to finally
  172. * starting, the openmrs runtime properties are already in place A static load count is kept to
  173. * count the number of times this class has been loaded.
  174. *
  175. * @see #getLoadCount()
  176. */
  177. public BaseContextSensitiveTest() {
  178.  
  179. Thread.currentThread().setContextClassLoader(OpenmrsClassLoader.getInstance());
  180.  
  181. Properties props = getRuntimeProperties();
  182.  
  183. if (log.isDebugEnabled())
  184. log.debug("props: " + props);
  185.  
  186. Context.setRuntimeProperties(props);
  187.  
  188. loadCount++;
  189.  
  190. instance = this;
  191. }
  192.  
  193. /**
  194. * Initializes fields annotated with {@link Mock}.
  195. *
  196. * @since 1.11, 1.10, 1.9.9
  197. */
  198. @BeforeEach
  199. public void initMocks() {
  200. MockitoAnnotations.initMocks(this);
  201. }
  202.  
  203. /**
  204. * @since 1.11, 1.10, 1.9.9
  205. */
  206. @AfterEach
  207. public void revertContextMocks() {
  208. contextMockHelper.revertMocks();
  209. }
  210.  
  211. /**
  212. * Modules should extend {@link BaseModuleContextSensitiveTest}, not this class. If they extend
  213. * this class, then they won't work right when run in batches.
  214. *
  215. * @throws Exception
  216. */
  217. @BeforeEach
  218. public void checkNotModule() throws Exception {
  219. if (this.getClass().getPackage().toString().contains("org.openmrs.module.")
  220. && !(this instanceof BaseModuleContextSensitiveTest)) {
  221. throw new RuntimeException(
  222. "Module unit test classes should extend BaseModuleContextSensitiveTest, not just BaseContextSensitiveTest");
  223. }
  224. }
  225.  
  226. /**
  227. * Allows to ignore the test if the environment does not match the given parameters.
  228. *
  229. * @param openmrsPlatformVersion
  230. * @param modules
  231. * @since 1.11.3, 1.10.2, 1.9.9
  232. */
  233. public void assumeOpenmrsProfile(String openmrsPlatformVersion, String... modules) {
  234. OpenmrsProfileExcludeFilter filter = new OpenmrsProfileExcludeFilter();
  235. Map<String, Object> profile = new HashMap<>();
  236. profile.put("openmrsPlatformVersion", openmrsPlatformVersion);
  237. if (modules != null) {
  238. profile.put("modules", modules);
  239. } else {
  240. profile.put("modules", new String[0]);
  241. }
  242. String errorMessage = "Ignored. Expected profile: {openmrsPlatformVersion=" + openmrsPlatformVersion + ", modules=["
  243. + StringUtils.join((String[]) profile.get("modules"), ", ") + "]}";
  244. assumeTrue(filter.matchOpenmrsProfileAttributes(profile), errorMessage);
  245. }
  246.  
  247. /**
  248. * Allows to ignore the test if the given modules are not running.
  249. *
  250. * @param module in the format moduleId:version
  251. * @param modules additional list of modules in the format moduleId:version
  252. * @since 1.11.3, 1.10.2, 1.9.9
  253. */
  254. public void assumeOpenmrsModules(String module, String... modules) {
  255. String[] allModules = ArrayUtils.addAll(modules, module);
  256. assumeOpenmrsProfile(null, allModules);
  257. }
  258.  
  259. /**
  260. * Allows to ignore the test if the environment does not match the given OpenMRS version.
  261. *
  262. * @param openmrsPlatformVersion
  263. * @since 1.11.3, 1.10.2, 1.9.9
  264. */
  265. public void assumeOpenmrsPlatformVersion(String openmrsPlatformVersion) {
  266. assumeOpenmrsProfile(openmrsPlatformVersion);
  267. }
  268.  
  269. /**
  270. * Get the number of times this class has been loaded. This is a rough approx of how many tests
  271. * have been run so far. This can be used to determine if the test is being run in a standalone
  272. * context or if other tests have been run before.
  273. *
  274. * @return number of times this class has been loaded
  275. */
  276. public Integer getLoadCount() {
  277. return loadCount;
  278. }
  279.  
  280. /**
  281. * Used for runtime properties. The default is "openmrs" because most people will use that as
  282. * the default. If your webapp and runtime properties are under a different name, override this
  283. * method in your tests
  284. *
  285. * @return String webapp name to assume when looking up the runtime properties
  286. */
  287. public String getWebappName() {
  288. return "openmrs";
  289. }
  290.  
  291. /**
  292. * Mimics org.openmrs.web.Listener.getRuntimeProperties() Overrides the database connection
  293. * properties if the user wants an in-memory database
  294. *
  295. * @return Properties runtime
  296. */
  297. public Properties getRuntimeProperties() {
  298.  
  299. // cache the properties for subsequent calls
  300. if (runtimeProperties == null)
  301. runtimeProperties = TestUtil.getRuntimeProperties(getWebappName());
  302.  
  303. // if we're using the in-memory hypersonic database, add those
  304. // connection properties here to override what is in the runtime
  305. // properties
  306. if (useInMemoryDatabase()) {
  307. runtimeProperties.setProperty(Environment.DIALECT, H2Dialect.class.getName());
  308. String url = "jdbc:h2:mem:openmrs;DB_CLOSE_DELAY=30;LOCK_TIMEOUT=10000";
  309. runtimeProperties.setProperty(Environment.URL, url);
  310. runtimeProperties.setProperty(Environment.DRIVER, "org.h2.Driver");
  311. runtimeProperties.setProperty(Environment.USER, "sa");
  312. runtimeProperties.setProperty(Environment.PASS, "");
  313.  
  314. // these properties need to be set in case the user has this exact
  315. // phrasing in their runtime file.
  316. runtimeProperties.setProperty("connection.username", "sa");
  317. runtimeProperties.setProperty("connection.password", "");
  318. runtimeProperties.setProperty("connection.url", url);
  319.  
  320. // automatically create the tables defined in the hbm files
  321. runtimeProperties.setProperty(Environment.HBM2DDL_AUTO, "create-drop");
  322. }
  323. else {
  324. String url = System.getProperty("databaseUrl");
  325. String username = System.getProperty("databaseUsername");
  326. String password = System.getProperty("databasePassword");
  327.  
  328. runtimeProperties.setProperty(Environment.URL, url);
  329. runtimeProperties.setProperty(Environment.DRIVER, System.getProperty("databaseDriver"));
  330. runtimeProperties.setProperty(Environment.USER, username);
  331. runtimeProperties.setProperty(Environment.PASS, password);
  332. runtimeProperties.setProperty(Environment.DIALECT, System.getProperty("databaseDialect"));
  333.  
  334. // these properties need to be set in case the user has this exact
  335. // phrasing in their runtime file.
  336. runtimeProperties.setProperty("connection.username", username);
  337. runtimeProperties.setProperty("connection.password", password);
  338. runtimeProperties.setProperty("connection.url", url);
  339.  
  340. //for the first time, automatically create the tables defined in the hbm files
  341. //after that, just update, if there are any changes. This is for performance reasons.
  342. runtimeProperties.setProperty(Environment.HBM2DDL_AUTO, "update");
  343. }
  344.  
  345. // we don't want to try to load core modules in tests
  346. runtimeProperties.setProperty(ModuleConstants.IGNORE_CORE_MODULES_PROPERTY, "true");
  347.  
  348. try {
  349. File tempappdir = File.createTempFile("appdir-for-unit-tests-", "");
  350. tempappdir.delete(); // so we can make it into a directory
  351. tempappdir.mkdir(); // turn it into a directory
  352. tempappdir.deleteOnExit(); // clean up when we're done with tests
  353.  
  354. runtimeProperties.setProperty(OpenmrsConstants.APPLICATION_DATA_DIRECTORY_RUNTIME_PROPERTY, tempappdir
  355. .getAbsolutePath());
  356. OpenmrsUtil.setApplicationDataDirectory(tempappdir.getAbsolutePath());
  357. }
  358. catch (IOException e) {
  359. log.error("Unable to create temp dir", e);
  360. }
  361.  
  362. return runtimeProperties;
  363. }
  364.  
  365. /**
  366. * This method provides the credentials to authenticate the user that is authenticated through the base setup.
  367. * This method can be overridden when setting up test application contexts that are *not* using the default authentication scheme.
  368. *
  369. * @return The credentials to use for base setup authentication.
  370. * @since 2.3.0
  371. */
  372. protected Credentials getCredentials() {
  373. return new UsernamePasswordCredentials("admin", "test");
  374. }
  375.  
  376. /**
  377. * Authenticate to the Context. A popup box will appear asking the current user to enter
  378. * credentials unless there is a junit.username and junit.password defined in the runtime
  379. * properties
  380. *
  381. * @throws Exception
  382. */
  383. public void authenticate() {
  384. if (Context.isAuthenticated() && Context.getAuthenticatedUser().equals(authenticatedUser)) {
  385. return;
  386. }
  387.  
  388. try {
  389. Context.authenticate(getCredentials());
  390. authenticatedUser = Context.getAuthenticatedUser();
  391. return;
  392. }
  393. catch (ContextAuthenticationException wrongCredentialsError) {
  394. if (useInMemoryDatabase()) {
  395. // if we get here the user is using some database other than the standard
  396. // in-memory database, prompt the user for input
  397. log.error("For some reason we couldn't auth as admin:test ?!", wrongCredentialsError);
  398. }
  399. }
  400.  
  401. Integer attempts = 0;
  402.  
  403. // TODO: how to make this a locale specific message for the user to see?
  404. String message = null;
  405.  
  406. // only need to authenticate once per session
  407. while (!Context.isAuthenticated() && attempts < 3) {
  408.  
  409. // look in the runtime properties for a defined username and
  410. // password first
  411. String junitusername = null;
  412. String junitpassword = null;
  413.  
  414. try {
  415. Properties props = this.getRuntimeProperties();
  416. junitusername = props.getProperty("junit.username");
  417. junitpassword = props.getProperty("junit.password");
  418. }
  419. catch (Exception e) {
  420. // if anything happens just default to asking the user
  421. }
  422.  
  423. String[] credentials = null;
  424.  
  425. // ask the user for creds if no junit username/pass defined
  426. // in the runtime properties or if that username/pass failed already
  427. if (junitusername == null || junitpassword == null || attempts > 0) {
  428. credentials = askForUsernameAndPassword(message);
  429. // credentials are null if the user clicked "cancel" in popup
  430. if (credentials == null)
  431. return;
  432. } else
  433. credentials = new String[] { junitusername, junitpassword };
  434.  
  435. // try to authenticate to the Context with either the runtime
  436. // defined credentials or the user supplied credentials from the
  437. // popup
  438. try {
  439. Context.authenticate(credentials[0], credentials[1]);
  440. authenticatedUser = Context.getAuthenticatedUser();
  441. }
  442. catch (ContextAuthenticationException e) {
  443. message = "Invalid username/password. Try again.";
  444. }
  445.  
  446. attempts++;
  447. }
  448. }
  449.  
  450. /**
  451. * Utility method for obtaining username and password through Swing interface for tests. Any
  452. * tests extending the org.openmrs.BaseTest class may simply invoke this method by name.
  453. * Username and password are returned in a two-member String array. If the user aborts, null is
  454. * returned. <b> <em>Do not call for non-interactive tests, since this method will try to
  455. * render an interactive dialog box for authentication!</em></b>
  456. *
  457. * @param message string to display above username field
  458. * @return Two-member String array containing username and password, respectively, or
  459. * <code>null</code> if user aborts dialog
  460. */
  461. public static synchronized String[] askForUsernameAndPassword(String message) {
  462.  
  463. try {
  464. UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
  465. }
  466. catch (Exception e) {
  467.  
  468. }
  469.  
  470. if (message == null || "".equals(message))
  471. message = "Enter username/password to authenticate to OpenMRS...";
  472.  
  473. JPanel panel = new JPanel(new GridBagLayout());
  474. JLabel usernameLabel = new JLabel("Username");
  475. usernameLabel.setFont(font);
  476. usernameField = new JTextField(20);
  477. usernameField.setFont(font);
  478. JLabel passwordLabel = new JLabel("Password");
  479. passwordLabel.setFont(font);
  480. JPasswordField passwordField = new JPasswordField(20);
  481. passwordField.setFont(font);
  482. panel.add(usernameLabel, new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.EAST,
  483. GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 5, 0));
  484. panel.add(usernameField, new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.WEST,
  485. GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
  486. panel.add(passwordLabel, new GridBagConstraints(0, 1, 1, 1, 0, 0, GridBagConstraints.EAST,
  487. GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 5, 0));
  488. panel.add(passwordField, new GridBagConstraints(1, 1, 1, 1, 0, 0, GridBagConstraints.WEST,
  489. GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
  490.  
  491. frame = new JFrame();
  492. Window window = new Window(frame);
  493. frame.setVisible(true);
  494. frame.setTitle("JUnit Test Credentials");
  495.  
  496. // We use a TimerTask to force focus on username, but still use
  497. // JOptionPane for model dialog
  498. TimerTask later = new TimerTask() {
  499.  
  500. @Override
  501. public void run() {
  502. if (frame != null) {
  503. // bring the dialog's window to the front
  504. frame.toFront();
  505. usernameField.grabFocus();
  506. }
  507. }
  508. };
  509. // try setting focus half a second from now
  510. new Timer().schedule(later, 500);
  511.  
  512. // attention grabber for those people that aren't as observant
  513. TimerTask laterStill = new TimerTask() {
  514.  
  515. @Override
  516. public void run() {
  517. if (frame != null) {
  518. frame.toFront(); // bring the dialog's window to the
  519. // front
  520. usernameField.grabFocus();
  521. }
  522. }
  523. };
  524. // if the user hasn't done anything in 10 seconds, tell them the window
  525. // is there
  526. new Timer().schedule(laterStill, 10000);
  527.  
  528. // show the dialog box
  529. int response = JOptionPane.showConfirmDialog(window, panel, message, JOptionPane.OK_CANCEL_OPTION);
  530.  
  531. // clear out the window so the timer doesn't screw up
  532. laterStill.cancel();
  533. frame.setVisible(false);
  534. window.setVisible(false);
  535. frame = null;
  536.  
  537. // response of 2 is the cancel button, response of -1 is the little red
  538. // X in the top right
  539. return (response == 2 || response == -1 ? null : new String[] { usernameField.getText(),
  540. String.valueOf(passwordField.getPassword()) });
  541. }
  542.  
  543. /**
  544. * Override this method to turn on/off the in-memory database. The default is to use the
  545. * in-memory database. When this method returns false, the database defined by the runtime
  546. * properties is used instead
  547. *
  548. * @return true/false whether or not to use an in memory database
  549. */
  550. public Boolean useInMemoryDatabase() {
  551. return !"false".equals(System.getProperty("useInMemoryDatabase"));
  552. }
  553.  
  554. /**
  555. * Get the database connection currently in use by the testing framework.
  556. * <p>
  557. * Note that if you commit a transaction, any changes done by a test will not be rolled back and
  558. * you will need to clean up yourself by calling for example {@link #deleteAllData()}.
  559. *
  560. * @return Connection jdbc connection to the database
  561. */
  562. public Connection getConnection() {
  563. SessionFactory sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory");
  564.  
  565. return sessionFactory.getCurrentSession().doReturningWork(connection -> connection);
  566. }
  567.  
  568. /**
  569. * This initializes the empty in-memory database with some rows in order to actually run some
  570. * tests
  571. *
  572. * @throws SQLException
  573. * @throws Exception
  574. */
  575. public void initializeInMemoryDatabase() throws SQLException {
  576. //Don't allow the user to overwrite data
  577. if (!useInMemoryDatabase())
  578. throw new RuntimeException(
  579. "You shouldn't be initializing a NON in-memory database. Consider unoverriding useInMemoryDatabase");
  580.  
  581. //Because creator property in the superclass is mapped with optional set to false, the autoddl tool marks the
  582. //column as not nullable but for person it is actually nullable, we need to first drop the constraint from
  583. //person.creator column, historically this was to allow inserting the very first row. Ideally, this should not
  584. //be necessary outside of tests because tables are created using liquibase and not autoddl
  585. dropNotNullConstraint("person", "creator");
  586. setAutoIncrementOnTablesWithNativeIfNotAssignedIdentityGenerator();
  587. executeDataSet(INITIAL_XML_DATASET_PACKAGE_PATH);
  588. }
  589.  
  590. public void setAutoIncrementOnTablesWithNativeIfNotAssignedIdentityGenerator() throws SQLException {
  591. /*
  592. * Hbm2ddl used in tests creates primary key columns, which are not auto incremented if
  593. * NativeIfNotAssignedIdentityGenerator is used. We need to alter those columns in tests.
  594. */
  595. List<String> tables = Collections.singletonList("concept");
  596. for (String table : tables) {
  597. getConnection().prepareStatement("ALTER TABLE " + table + " ALTER COLUMN " + table + "_id INT AUTO_INCREMENT")
  598. .execute();
  599. }
  600. }
  601.  
  602. /**
  603. * Drops the not null constraint from the the specified column in the specified table
  604. *
  605. * @param columnName the column from which to remove the constraint
  606. * @param tableName the table that contains the column
  607. * @throws SQLException
  608. */
  609. protected void dropNotNullConstraint(String tableName, String columnName) throws SQLException {
  610. if (!useInMemoryDatabase()) {
  611. throw new RuntimeException("Altering column nullability is not supported for a non in-memory database");
  612. }
  613. final String sql = "ALTER TABLE " + tableName + " ALTER COLUMN " + columnName + " SET NULL";
  614. DatabaseUtil.executeSQL(getConnection(), sql, false);
  615. }
  616.  
  617. /**
  618. * Note that with the H2 DB this operation always commits an open transaction.
  619. *
  620. * @param connection
  621. * @throws SQLException
  622. */
  623. private void turnOnDBConstraints(Connection connection) throws SQLException {
  624. String constraintsOnSql;
  625. if (useInMemoryDatabase()) {
  626. constraintsOnSql = "SET REFERENTIAL_INTEGRITY TRUE";
  627. } else {
  628. constraintsOnSql = "SET FOREIGN_KEY_CHECKS=1;";
  629. }
  630. PreparedStatement ps = connection.prepareStatement(constraintsOnSql);
  631. ps.execute();
  632. ps.close();
  633. }
  634.  
  635. private void turnOffDBConstraints(Connection connection) throws SQLException {
  636. String constraintsOffSql;
  637. if (useInMemoryDatabase()) {
  638. constraintsOffSql = "SET REFERENTIAL_INTEGRITY FALSE";
  639. } else {
  640. constraintsOffSql = "SET FOREIGN_KEY_CHECKS=0;";
  641. }
  642. PreparedStatement ps = connection.prepareStatement(constraintsOffSql);
  643. ps.execute();
  644. ps.close();
  645. }
  646.  
  647. /**
  648. * Used by {@link #executeDataSet(String)} to cache the parsed xml files. This speeds up
  649. * subsequent runs of the dataset
  650. */
  651. private static Map<String, IDataSet> cachedDatasets = new HashMap<>();
  652.  
  653. /**
  654. * Runs the flat xml data file at the classpath location specified by
  655. * <code>datasetFilename</code> This is a convenience method. It simply creates an
  656. * {@link IDataSet} and calls {@link #executeDataSet(IDataSet)}
  657. *
  658. * @param datasetFilename String path/filename on the classpath of the xml data set to clean
  659. * insert into the current database
  660. * @see #getConnection()
  661. * @see #executeDataSet(IDataSet)
  662. */
  663. public void executeDataSet(String datasetFilename) {
  664.  
  665. // try to get the given filename from the cache
  666. IDataSet xmlDataSetToRun = cachedDatasets.get(datasetFilename);
  667.  
  668. // if we didn't find it in the cache, load it
  669. if (xmlDataSetToRun == null) {
  670. File file = new File(datasetFilename);
  671.  
  672. InputStream fileInInputStreamFormat = null;
  673. Reader reader = null;
  674. try {
  675. try {
  676. // try to load the file if its a straight up path to the file or
  677. // if its a classpath path to the file
  678. if (file.exists()) {
  679. fileInInputStreamFormat = new FileInputStream(datasetFilename);
  680. } else {
  681. fileInInputStreamFormat = getClass().getClassLoader().getResourceAsStream(datasetFilename);
  682. if (fileInInputStreamFormat == null)
  683. throw new FileNotFoundException("Unable to find '" + datasetFilename + "' in the classpath");
  684. }
  685.  
  686. reader = new InputStreamReader(fileInInputStreamFormat, StandardCharsets.UTF_8);
  687. ReplacementDataSet replacementDataSet = new ReplacementDataSet(
  688. new FlatXmlDataSet(reader, false, true, false));
  689. replacementDataSet.addReplacementObject("[NULL]", null);
  690. xmlDataSetToRun = replacementDataSet;
  691.  
  692. reader.close();
  693. }
  694. catch (DataSetException | IOException e) {
  695. throw new DatabaseUnitRuntimeException(e);
  696. }
  697. }
  698. finally {
  699. IOUtils.closeQuietly(fileInInputStreamFormat);
  700. IOUtils.closeQuietly(reader);
  701. }
  702.  
  703. // cache the xmldataset for future runs of this file
  704. cachedDatasets.put(datasetFilename, xmlDataSetToRun);
  705. }
  706.  
  707. executeDataSet(xmlDataSetToRun);
  708. }
  709.  
  710. /**
  711. * Runs the large flat xml dataset. It does not cache the file as opposed to
  712. * {@link #executeDataSet(String)}.
  713. *
  714. * @param datasetFilename
  715. * @throws Exception
  716. * @since 1.10
  717. */
  718. public void executeLargeDataSet(String datasetFilename) throws Exception {
  719. InputStream inputStream = null;
  720. try {
  721. final File file = new File(datasetFilename);
  722. if (file.exists()) {
  723. inputStream = new FileInputStream(datasetFilename);
  724. } else {
  725. inputStream = getClass().getClassLoader().getResourceAsStream(datasetFilename);
  726. if (inputStream == null)
  727. throw new FileNotFoundException("Unable to find '" + datasetFilename + "' in the classpath");
  728. }
  729.  
  730. final FlatXmlProducer flatXmlProducer = new FlatXmlProducer(new InputSource(inputStream));
  731. final StreamingDataSet streamingDataSet = new StreamingDataSet(flatXmlProducer);
  732.  
  733. final ReplacementDataSet replacementDataSet = new ReplacementDataSet(streamingDataSet);
  734. replacementDataSet.addReplacementObject("[NULL]", null);
  735.  
  736. executeDataSet(replacementDataSet);
  737.  
  738. inputStream.close();
  739. }
  740. finally {
  741. IOUtils.closeQuietly(inputStream);
  742. }
  743. }
  744.  
  745. /**
  746. * Runs the xml data file at the classpath location specified by <code>datasetFilename</code>
  747. * using XmlDataSet. It simply creates an {@link IDataSet} and calls
  748. * {@link #executeDataSet(IDataSet)}. <br>
  749. * <br>
  750. * This method is different than {@link #executeDataSet(String)} in that this one does not
  751. * expect a flat file xml but instead a true XmlDataSet. <br>
  752. * <br>
  753. * In addition, there is no replacing of [NULL] values in strings.
  754. *
  755. * @param datasetFilename String path/filename on the classpath of the xml data set to clean
  756. * insert into the current database
  757. * @see #getConnection()
  758. * @see #executeDataSet(IDataSet)
  759. */
  760. public void executeXmlDataSet(String datasetFilename) throws Exception {
  761.  
  762. // try to get the given filename from the cache
  763. IDataSet xmlDataSetToRun = cachedDatasets.get(datasetFilename);
  764.  
  765. // if we didn't find it in the cache, load it
  766. if (xmlDataSetToRun == null) {
  767. File file = new File(datasetFilename);
  768.  
  769. InputStream fileInInputStreamFormat = null;
  770.  
  771. try {
  772. // try to load the file if its a straight up path to the file or
  773. // if its a classpath path to the file
  774. if (file.exists())
  775. fileInInputStreamFormat = new FileInputStream(datasetFilename);
  776. else {
  777. fileInInputStreamFormat = getClass().getClassLoader().getResourceAsStream(datasetFilename);
  778. if (fileInInputStreamFormat == null)
  779. throw new FileNotFoundException("Unable to find '" + datasetFilename + "' in the classpath");
  780. }
  781.  
  782. XmlDataSet xmlDataSet = null;
  783. xmlDataSet = new XmlDataSet(fileInInputStreamFormat);
  784. xmlDataSetToRun = xmlDataSet;
  785.  
  786. fileInInputStreamFormat.close();
  787. }
  788. finally {
  789. IOUtils.closeQuietly(fileInInputStreamFormat);
  790. }
  791.  
  792. // cache the xmldataset for future runs of this file
  793. cachedDatasets.put(datasetFilename, xmlDataSetToRun);
  794. }
  795.  
  796. executeDataSet(xmlDataSetToRun);
  797. }
  798.  
  799. /**
  800. * Run the given dataset specified by the <code>dataset</code> argument
  801. *
  802. * @param dataset IDataSet to run on the current database used by Spring
  803. * @see #getConnection()
  804. */
  805. public void executeDataSet(IDataSet dataset) {
  806. try {
  807. Connection connection = getConnection();
  808.  
  809. IDatabaseConnection dbUnitConn = setupDatabaseConnection(connection);
  810.  
  811. //Do the actual update/insert:
  812. //insert new rows, update existing rows, and leave others alone
  813. DatabaseOperation.REFRESH.execute(dbUnitConn, dataset);
  814. }
  815. catch (DatabaseUnitException | SQLException e) {
  816. throw new DatabaseUnitRuntimeException(e);
  817. }
  818. }
  819.  
  820. private IDatabaseConnection setupDatabaseConnection(Connection connection) throws DatabaseUnitException {
  821. IDatabaseConnection dbUnitConn = new DatabaseConnection(connection);
  822.  
  823. if (useInMemoryDatabase()) {
  824. //Setup the db connection to use H2 config.
  825. DatabaseConfig config = dbUnitConn.getConfig();
  826. config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new H2DataTypeFactory());
  827. }
  828.  
  829. return dbUnitConn;
  830. }
  831.  
  832. /**
  833. * This is a convenience method to clear out all rows in all tables in the current connection.
  834. * <p>
  835. * This operation always results in a commit.
  836. *
  837. * @throws Exception
  838. */
  839. public void deleteAllData() {
  840. try {
  841. Context.clearSession();
  842.  
  843. Connection connection = getConnection();
  844.  
  845. turnOffDBConstraints(connection);
  846.  
  847. IDatabaseConnection dbUnitConn = setupDatabaseConnection(connection);
  848.  
  849. // find all the tables for this connection
  850. ResultSet resultSet = connection.getMetaData().getTables(null, "PUBLIC", "%", null);
  851. DefaultDataSet dataset = new DefaultDataSet();
  852. while (resultSet.next()) {
  853. String tableName = resultSet.getString(3);
  854. dataset.addTable(new DefaultTable(tableName));
  855. }
  856.  
  857. // do the actual deleting/truncating
  858. DatabaseOperation.DELETE_ALL.execute(dbUnitConn, dataset);
  859.  
  860. turnOnDBConstraints(connection);
  861.  
  862. connection.commit();
  863.  
  864. updateSearchIndex();
  865.  
  866. isBaseSetup = false;
  867. }
  868. catch (SQLException | DatabaseUnitException e) {
  869. throw new DatabaseUnitRuntimeException(e);
  870. }
  871. }
  872.  
  873. /**
  874. * Method to clear the hibernate cache
  875. */
  876. @BeforeEach
  877. public void clearHibernateCache() {
  878. SessionFactory sf = (SessionFactory) applicationContext.getBean("sessionFactory");
  879. sf.getCache().evictCollectionRegions();
  880. sf.getCache().evictEntityRegions();
  881. }
  882.  
  883. /**
  884. * This method is run before all test methods that extend this {@link BaseContextSensitiveTest}
  885. * unless you annotate your method with the "@SkipBaseSetup" annotation After running this
  886. * method an in-memory database will be available that has the content of the rows from
  887. * {@link #INITIAL_XML_DATASET_PACKAGE_PATH} and {@link #EXAMPLE_XML_DATASET_PACKAGE_PATH} xml
  888. * files. This method will also ask to be authenticated against the current Context and
  889. * database. The {@link #initializeInMemoryDatabase()} method has a user of admin:test.
  890. * <p>
  891. * If you annotate a test with "@SkipBaseSetup", this method will call {@link #deleteAllData()},
  892. * but only if you use the in memory DB.
  893. *
  894. * @throws SQLException
  895. * @see SkipBaseSetup
  896. * @see SkipBaseSetupAnnotationExecutionListener
  897. * @see #initializeInMemoryDatabase()
  898. * @see #authenticate()
  899. */
  900. @BeforeEach
  901. public void baseSetupWithStandardDataAndAuthentication() throws SQLException {
  902. // Open a session if needed
  903. if (!Context.isSessionOpen()) {
  904. Context.openSession();
  905. }
  906.  
  907. // The skipBaseSetup flag is controlled by the @SkipBaseSetup annotation. if (useInMemoryDatabase()) {
  908. if (!skipBaseSetup) {
  909. if (!isBaseSetup) {
  910.  
  911. deleteAllData();
  912.  
  913. if (useInMemoryDatabase()) {
  914. initializeInMemoryDatabase();
  915. }
  916. else {
  917. executeDataSet(INITIAL_XML_DATASET_PACKAGE_PATH);
  918. }
  919.  
  920. executeDataSet(EXAMPLE_XML_DATASET_PACKAGE_PATH);
  921.  
  922. //Commit so that it is not rolled back after a test.
  923. getConnection().commit();
  924.  
  925. updateSearchIndex();
  926.  
  927. isBaseSetup = true;
  928. }
  929.  
  930. authenticate();
  931. } else {
  932. if (isBaseSetup) {
  933. deleteAllData();
  934. }
  935. }
  936.  
  937. Context.clearSession();
  938. }
  939.  
  940. public Class<?>[] getIndexedTypes() {
  941. return new Class<?>[] { ConceptName.class, Drug.class, PersonName.class, PersonAttribute.class,
  942. PatientIdentifier.class};
  943. }
  944.  
  945. /**
  946. * It needs to be call if you want to do a concept search after you modify a concept in a test.
  947. * It is because index is automatically updated only after transaction is committed, which
  948. * happens only at the end of a test in our transactional tests.
  949. */
  950. public void updateSearchIndex() {
  951. for (Class<?> indexType : getIndexedTypes()) {
  952. Context.updateSearchIndexForType(indexType);
  953. }
  954. }
  955.  
  956. @AfterEach
  957. public void clearSessionAfterEachTest() {
  958. // clear the session to make sure nothing is cached, etc
  959. Context.clearSession();
  960.  
  961. // needed because the authenticatedUser is the only object that sticks
  962. // around after tests and the clearSession call
  963. if (Context.isSessionOpen())
  964. Context.logout();
  965. }
  966.  
  967. /**
  968. * Called after each test class. This is called once per test class that extends
  969. * {@link BaseContextSensitiveTest}. Needed so that "unit of work" that is the test class is
  970. * surrounded by a pair of open/close session calls.
  971. *
  972. * @throws Exception
  973. */
  974. @AfterAll
  975. public static void closeSessionAfterEachClass() throws Exception {
  976. //Some tests add data via executeDataset()
  977. //We need to delete it in order not to interfere with others
  978. if (instance != null) {
  979. try {
  980. instance.deleteAllData();
  981. }
  982. catch (Exception ex) {
  983. //No need to worry about this
  984. }
  985. instance = null;
  986. }
  987.  
  988. // clean up the session so we don't leak memory
  989. if (Context.isSessionOpen()) {
  990. Context.closeSession();
  991. }
  992. }
  993.  
  994. /**
  995. * Instance variable used by the {@link #baseSetupWithStandardDataAndAuthentication()} method to
  996. * know whether the current "@Test" method has asked to be _not_ do the initialize/standard
  997. * data/authenticate
  998. *
  999. * @see SkipBaseSetup
  1000. * @see SkipBaseSetupAnnotationExecutionListener
  1001. * @see #baseSetupWithStandardDataAndAuthentication()
  1002. */
  1003. private boolean skipBaseSetup = false;
  1004.  
  1005. /**
  1006. * Don't run the {@link #setupDatabaseWithStandardData()} method. This means that the associated
  1007. * "@Test" must call one of these:
  1008. *
  1009. * <pre>
  1010. * * initializeInMemoryDatabase() ;
  1011. * * executeDataSet(EXAMPLE_DATA_SET);
  1012. * * Authenticate
  1013. * </pre>
  1014. *
  1015. * on its own if any of those results are needed. This method is called before all "@Test"
  1016. * methods that have been annotated with the "@SkipBaseSetup" annotation.
  1017. *
  1018. * @throws Exception
  1019. * @see SkipBaseSetup
  1020. * @see SkipBaseSetupAnnotationExecutionListener
  1021. * @see #baseSetupWithStandardDataAndAuthentication()
  1022. */
  1023. public void skipBaseSetup() throws Exception {
  1024. skipBaseSetup = true;
  1025. }
  1026.  
  1027. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement