/** * The contents of this file are subject to the OpenMRS Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://license.openmrs.org * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * Copyright (C) OpenMRS, LLC. All Rights Reserved. */ package org.openmrs.module.sync; import junit.framework.Assert; import org.junit.Test; import org.openmrs.Patient; import org.openmrs.PatientIdentifier; import org.openmrs.User; import org.openmrs.api.context.Context; import org.openmrs.module.sync.api.SyncService; import org.openmrs.module.sync.serialization.Record; import org.openmrs.util.OpenmrsConstants; import org.springframework.test.annotation.NotTransactional; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.UUID; /** * This test is written in response to a production issue we have discovered, in which somehow an invalid sync record * is persisted and sent to the parent. The problem manifests when the parent attempts to play back this record, finds * that the underlying object is invalid (eg. is missing a database required field), and fails to save, yet a sync record * for this item saves nonetheless (and saves in such a way that the original uuid is the same as the record uuid, so it * appears as a new record to subsequent children, thus causing an effect of bouncing back and forth forever and filling * up the sync record table with many invalid, duplicate sync records. */ public class SyncInvalidRecordTest extends SyncBaseTest { String syncRecordUuid = "e33761c7-25e0-40ca-b14a-58f99320c25e"; @Override public String getInitialDataset() { try { return "org/openmrs/module/sync/include/" + new TestUtil().getTestDatasetFilename("syncCreateTest"); } catch (Exception e) { throw new RuntimeException(e); } } @Override protected List<SyncRecord> getSyncRecords() throws Exception { List<SyncRecord> ret = new ArrayList<SyncRecord>(); User u = Context.getAuthenticatedUser(); SyncRecord sr = new SyncRecord(); sr.setUuid(syncRecordUuid); sr.setCreator(u.getId().toString()); sr.setDatabaseVersion(OpenmrsConstants.OPENMRS_VERSION_SHORT); sr.setTimestamp(new Date()); sr.setRetryCount(0); sr.setState(SyncRecordState.NEW); sr.setContainedClasses("org.openmrs.PatientIdentifier"); sr.setOriginalUuid(syncRecordUuid); SyncItem item = new SyncItem(); item.setContainedType(PatientIdentifier.class); item.setKey(new SyncItemKey<String>(UUID.randomUUID().toString(), String.class)); item.setState(SyncItemState.NEW); StringBuilder content = new StringBuilder(); content.append("<org.openmrs.PatientIdentifier>"); content.append("<patient type=\"org.openmrs.Patient\">").append(Context.getPatientService().getPatient(2).getUuid()).append("</patient>"); content.append("<voided type=\"boolean\">false</voided>"); content.append("<dateCreated type=\"timestamp\">2014-07-07T10:19:57.940-0400</dateCreated>"); content.append("<uuid type=\"string\">54d3ca4a-d1ee-421c-a74e-5336c7519888</uuid>"); content.append("<preferred type=\"boolean\">false</preferred>"); content.append("<creator type=\"org.openmrs.User\">").append(u.getUuid()).append("</creator>"); content.append("</org.openmrs.PatientIdentifier>"); item.setContent(content.toString()); sr.addItem(item); ret.add(sr); return ret; } @Override protected void repopulateDB(String xmlFileToExecute) throws Exception { super.repopulateDB(xmlFileToExecute); Context.addProxyPrivilege("SQL Level Access"); Context.getAdministrationService().executeSQL("alter table patient_identifier alter column identifier set not null", false); } @Test @NotTransactional public void shouldAddInvalidIdentifierTest() throws Exception { runSyncTest(new SyncTestHelper() { @Override public void runOnChild() { } @Override public void changedBeingApplied(List<SyncRecord> syncRecords, Record record) throws Exception { super.changedBeingApplied(syncRecords, record); // Assert that there are no sync records to start with assertNumSyncRecords(0); } @Override public void runOnParent() throws Exception { // Confirm that the identifier failed to save to the DB due to a validation error Patient p = Context.getPatientService().getPatient(2); Collection<PatientIdentifier> identifiers = p.getIdentifiers(); Assert.assertEquals(2, identifiers.size()); for (PatientIdentifier pi : p.getIdentifiers()) { Assert.assertNotSame("54d3ca4a-d1ee-421c-a74e-5336c7519888", pi.getUuid()); } // Since the identifier failed to save, confirm that no sync record was saved either assertNumSyncRecords(0); } }); } protected void assertNumSyncRecords(int num) throws Exception { int numFound = Context.getService(SyncService.class).getSyncRecords().size(); if (num != numFound) { System.out.println("Expected " + num + " sync records but found: " + numFound); org.openmrs.test.TestUtil.printOutTableContents(getConnection(), "sync_record"); } Assert.assertEquals(num, numFound); } }