1616
1717package org .labkey .ehr_billing ;
1818
19+ import org .junit .After ;
20+ import org .junit .Assert ;
21+ import org .junit .Before ;
22+ import org .junit .Test ;
1923import org .labkey .api .collections .CaseInsensitiveHashMap ;
2024import org .labkey .api .data .CompareType ;
2125import org .labkey .api .data .Container ;
3640import org .labkey .api .query .QueryUpdateServiceException ;
3741import org .labkey .api .security .User ;
3842import org .labkey .api .data .RuntimeSQLException ;
43+ import org .labkey .api .util .GUID ;
44+ import org .labkey .api .util .JunitUtil ;
45+ import org .labkey .api .util .TestContext ;
3946
4047import java .sql .SQLException ;
4148import java .util .ArrayList ;
4249import java .util .Arrays ;
4350import java .util .Collection ;
4451import java .util .Collections ;
52+ import java .util .Date ;
53+ import java .util .HashSet ;
4554import java .util .List ;
4655import java .util .Map ;
56+ import java .util .Set ;
4757
4858public class EHR_BillingManager
4959{
@@ -68,11 +78,11 @@ public List<String> deleteBillingRuns(User user, Container container, Collection
6878 TableInfo miscCharges = EHR_BillingSchema .getInstance ().getSchema ().getTable (EHR_BillingSchema .TABLE_MISC_CHARGES );
6979
7080 //create filters
71- SimpleFilter objectIdFilter = new SimpleFilter ( FieldKey . fromString ( "objectid" ) , pks , CompareType . IN );
72- SimpleFilter invoiceIdFilter = new SimpleFilter ( FieldKey . fromString ( "invoiceId" ) , pks , CompareType . IN );
73- SimpleFilter invoiceRunIdFilter = new SimpleFilter ( FieldKey . fromString ( "invoiceRunId" ) , pks , CompareType . IN );
81+ SimpleFilter objectIdFilter = createContainerScopedInFilter ( container , "objectid" , pks );
82+ SimpleFilter invoiceIdFilter = createContainerScopedInFilter ( container , "invoiceId" , pks );
83+ SimpleFilter invoiceRunIdFilter = createContainerScopedInFilter ( container , "invoiceRunId" , pks );
7484
75- SimpleFilter miscChargesFilter = new SimpleFilter ( FieldKey . fromString ( "invoiceId" ) , pks , CompareType . IN );
85+ SimpleFilter miscChargesFilter = createContainerScopedInFilter ( container , "invoiceId" , pks );
7686
7787 //perform the work
7888 List <String > ret = new ArrayList <>();
@@ -114,6 +124,11 @@ public List<String> deleteBillingRuns(User user, Container container, Collection
114124 return ret ;
115125 }
116126
127+ private SimpleFilter createContainerScopedInFilter (Container container , String columnName , Collection <String > values )
128+ {
129+ return SimpleFilter .createContainerFilter (container ).addInClause (FieldKey .fromString (columnName ), values );
130+ }
131+
117132 private void deleteInvoiceRuns (TableInfo tableInfo , Map <String , Object >[] rows , User user , Container container ) throws QueryUpdateServiceException , BatchValidationException , InvalidKeyException
118133 {
119134 if (rows .length >0 )
@@ -141,4 +156,127 @@ public Container getBillingContainer(Container c)
141156
142157 }
143158
144- }
159+ public static class TestCase extends Assert
160+ {
161+ private static final String FOLDER_A = "EHRBillingDeleteTestA" ;
162+ private static final String FOLDER_B = "EHRBillingDeleteTestB" ;
163+
164+ private User _user ;
165+ private Container _containerA ;
166+ private Container _containerB ;
167+ private String _runIdA ;
168+ private String _runIdB ;
169+
170+ @ Before
171+ public void setUp ()
172+ {
173+ _user = TestContext .get ().getUser ();
174+ deleteTestFolders ();
175+
176+ Container junit = JunitUtil .getTestContainer ();
177+ _containerA = createBillingFolder (junit , FOLDER_A );
178+ _containerB = createBillingFolder (junit , FOLDER_B );
179+
180+ _runIdA = insertBillingRun (_containerA );
181+ _runIdB = insertBillingRun (_containerB );
182+ }
183+
184+ @ After
185+ public void tearDown ()
186+ {
187+ deleteTestFolders ();
188+ }
189+
190+ @ Test
191+ public void testDeleteBillingRunsIsContainerScoped () throws Exception
192+ {
193+ EHR_BillingManager manager = EHR_BillingManager .get ();
194+ EHR_BillingSchema schema = EHR_BillingSchema .getInstance ();
195+
196+ // A testOnly preview issued from container A targeting container B's run must not see container B's rows
197+ for (String summary : manager .deleteBillingRuns (_user , _containerA , List .of (_runIdB ), true ))
198+ assertTrue ("Preview from another container should count 0 rows, but got: " + summary , summary .startsWith ("0 " ));
199+
200+ // An actual delete issued from container A targeting container B's run must leave container B untouched
201+ manager .deleteBillingRuns (_user , _containerA , List .of (_runIdB ), false );
202+ assertEquals ("invoiceRuns row in container B should survive a delete issued from container A" , 1 , containerRowCount (schema .getTableInvoiceRuns (), _containerB ));
203+ assertEquals ("invoice row in container B should survive a delete issued from container A" , 1 , containerRowCount (schema .getInvoice (), _containerB ));
204+ assertEquals ("invoicedItems row in container B should survive a delete issued from container A" , 1 , containerRowCount (schema .getTableInvoiceItems (), _containerB ));
205+ assertEquals ("miscCharges row in container B should still reference its invoice" , 1 , miscChargesWithInvoiceCount (_containerB ));
206+
207+ // Positive control: deleting a run from its own container removes its rows
208+ manager .deleteBillingRuns (_user , _containerA , List .of (_runIdA ), false );
209+ assertEquals ("invoiceRuns row in container A should be deleted" , 0 , containerRowCount (schema .getTableInvoiceRuns (), _containerA ));
210+ assertEquals ("invoice row in container A should be deleted" , 0 , containerRowCount (schema .getInvoice (), _containerA ));
211+ assertEquals ("invoicedItems row in container A should be deleted" , 0 , containerRowCount (schema .getTableInvoiceItems (), _containerA ));
212+ assertEquals ("miscCharges row in container A should be detached from the deleted invoice" , 0 , miscChargesWithInvoiceCount (_containerA ));
213+ assertEquals ("miscCharges row in container A should not be deleted" , 1 , containerRowCount (schema .getMiscCharges (), _containerA ));
214+ }
215+
216+ private Container createBillingFolder (Container parent , String name )
217+ {
218+ Container c = ContainerManager .createContainer (parent , name , _user );
219+ Set <Module > active = new HashSet <>(c .getActiveModules ());
220+ active .add (ModuleLoader .getInstance ().getModule (EHR_BillingModule .NAME ));
221+ c .setActiveModules (active , _user );
222+ return c ;
223+ }
224+
225+ private String insertBillingRun (Container c )
226+ {
227+ EHR_BillingSchema schema = EHR_BillingSchema .getInstance ();
228+ String runId = GUID .makeGUID ();
229+ String invoiceNumber = c .getName ();
230+
231+ Map <String , Object > run = new CaseInsensitiveHashMap <>();
232+ run .put ("objectid" , runId );
233+ run .put ("runDate" , new Date ());
234+ run .put ("container" , c .getId ());
235+ Table .insert (_user , schema .getTableInvoiceRuns (), run );
236+
237+ Map <String , Object > invoice = new CaseInsensitiveHashMap <>();
238+ invoice .put ("invoiceNumber" , invoiceNumber );
239+ invoice .put ("invoiceRunId" , runId );
240+ invoice .put ("container" , c .getId ());
241+ Table .insert (_user , schema .getInvoice (), invoice );
242+
243+ Map <String , Object > invoicedItem = new CaseInsensitiveHashMap <>();
244+ invoicedItem .put ("objectId" , GUID .makeGUID ());
245+ invoicedItem .put ("invoiceId" , runId );
246+ invoicedItem .put ("invoiceNumber" , invoiceNumber );
247+ invoicedItem .put ("container" , c .getId ());
248+ Table .insert (_user , schema .getTableInvoiceItems (), invoicedItem );
249+
250+ Map <String , Object > miscCharge = new CaseInsensitiveHashMap <>();
251+ miscCharge .put ("objectid" , GUID .makeGUID ());
252+ miscCharge .put ("invoiceId" , runId );
253+ miscCharge .put ("container" , c .getId ());
254+ Table .insert (_user , schema .getMiscCharges (), miscCharge );
255+
256+ return runId ;
257+ }
258+
259+ private long containerRowCount (TableInfo table , Container c )
260+ {
261+ return new TableSelector (table , SimpleFilter .createContainerFilter (c ), null ).getRowCount ();
262+ }
263+
264+ private long miscChargesWithInvoiceCount (Container c )
265+ {
266+ SimpleFilter filter = SimpleFilter .createContainerFilter (c );
267+ filter .addCondition (FieldKey .fromParts ("invoiceId" ), null , CompareType .NONBLANK );
268+ return new TableSelector (EHR_BillingSchema .getInstance ().getMiscCharges (), filter , null ).getRowCount ();
269+ }
270+
271+ private void deleteTestFolders ()
272+ {
273+ Container junit = JunitUtil .getTestContainer ();
274+ for (String name : List .of (FOLDER_A , FOLDER_B ))
275+ {
276+ Container c = junit .getChild (name );
277+ if (c != null )
278+ ContainerManager .delete (c , _user );
279+ }
280+ }
281+ }
282+ }
0 commit comments