Writing Qorus Tests in Java

In this blog post, you’ll learn how to write Java tests in Qorus. You’ll test the functionality developed in the “Poll SFTP Server For CSV Data And Import To DB” blog post series. You will begin by understanding the logic flow for the test and understand the code involved in each step.

ℹ️ Note: The complete test script is available in the examples/csv-sftp-to-db-import/ExampleImportCsvFileTest.java of the Qorus’ building blocks repo.

Logic Flow

1.Static Initialization
2. Set the job to run only on demand during the test
3. Create a test CSV file and put it on the SFTP server
4. Create a “RunJobResult” action and test for a COMPLETE status
5. Run the job and get the result
6. Check that a workflow order is created
7. Wait for order status
8. Get workflow order info and assert that it’s COMPLETE
9. Check if the test data has been added to the table
10. Check for duplicate file handling
11. Reset the job to run normally after the test completes
ℹ️ Note: Before you can run any Qorus tests, set the qorus-client.allow-test-execution to true in the /opt/qorus/etc/options file.

Qorus Imports and Classes

Import StatementDescription
qoremod.QorusClientBase.OMQ.$Constants Qore constants can be imported using the special $Constants class name; constants in a Qore namespace are imported automatically into a special class with this name as "static final" fields
qoremod.QorusClientBase.OMQ.Client.QorusSystemRestHelper The QorusSystemRestHelper class makes it easy to talk to the Qorus REST API. When run a Qorus server it handles data serialization, deserialization and local authentication

It will use the network encryption key, if it's readable, to send an API request to the server in order to obtain a token with system permissions to execute Qorus API calls

If the key is not readable, then it requires username and password configuration in the $OMQ_DIR/etc/options file at the qorus-client.client-url option
qoremod.QorusClientBase.OMQ.QorusClientApi QorusClientApi class implements the client interface to a Qorus server.
qoremod.QorusClientBase.OMQ.UserApi.UserApi UserApi is the main Qorus interface / utility class
qoremod.ssh2.SSH2.SFTPClient SFTPClient class allows Qorus code to communicate with SFTP servers. This is the object type that's returned from an sftp:// connection
qoremod.SqlUtil.AbstractTable The low-level parent class for the actual "Table" implementation in SqlUtil modules
org.qore.jni.QoreClosureMarkerImpl This marks a class that must have a call() method.

The call() method can be declared with any argument and return types and will be called when this object is converted to a Qore closure and the closure is called
org.qore.jni.Hash Java Hash class to make it easier to work with Qore hash data.

1. Static Initialization

Static Initialization Initialize the QorusSystemRestHelper object in the class’s static initialization, as the global qrest variable in the Qorus’ Qore client is not available in Java
static {
   try {
      qrest = new QorusSystemRestHelper();
   } catch (Throwable e) {
      throw new RuntimeException(e);
   }
}

2. Set the job to run only on demand during the test

You can set the job to run only on demand during the job, with:
qrest.put("jobs/example-import-csv-file/setActive", new Hash() {
   {
      put("active", false);
   }
});

3. Create A Test CSV File And Put It On The SFTP Server

Create a CSV file with test data:
String csv = ExampleImportCsvFileTest.getFileData(5);
String filename = String.format("StockReport-%s.csv", UUID.randomUUID().toString());
The getFileData above returns a string of CSV data that can be parsed by the interface
static String getFileData(int num_records) throws Throwable {
   return CsvHeader
      + Stream.generate(() -> getRecordString()).limit(num_records).collect(Collectors.joining());
}
The getRecordString method gets a single string for our CSV file with random data
static String getRecordString() {
   Hash prod = Products[ThreadLocalRandom.current().nextInt(0, Products.length)];
   return String.format("%s,%s,\"%s\",%d,%d,%d,%s\n",
      Stores[ThreadLocalRandom.current().nextInt(0, Stores.length)],
      prod.getString("code"),
      prod.getString("desc"),
      ThreadLocalRandom.current().nextInt(0, 10),
      ThreadLocalRandom.current().nextInt(0, 10),
      ThreadLocalRandom.current().nextInt(0, 10),
      Instant.now().toString());
}
ℹ️ Note: Products and Stores are static variables defined like so:
public static Hash[] Products = {
   new Hash() {
      {
         put("code", "SV300S37A/120G");
         put("desc", "Kingston SSDNow V300 120GB 7mm");
      }
   },
   new Hash() {
      {
         put("code", "SSDSC2BW120A401");
         put("desc", "Intel 530 120GB SSD bulk");
      }
   },
   new Hash() {
      {
         put("code", "MZ-7PD256BW");
         put("desc", "Samsung SSD840 256GB 7mm, Pro");
      }
   },
};

public static String[] Stores = {
   "Václavské náměstí",
   "Náměstí Míru",
   "Malostranské náměstí",
   "Komenského náměstí",
};

4. Create A "RunJobResult" Action And Test For A COMPLETE Status

The RunJobResult and Action classes are obtained from the QorusInterfaceTest package.
RunJobResult  action = new RunJobResult ($Constants.StatComplete);

5. Run The Job And Get The Result

Before executing the job, get the current number of records in the table:
AbstractTable inventory_example = UserApi.getSqlTable("omquser", "inventory_example");
longnum_recs = inventory_example.rowCount();
Execute the action, get the result and store it in a Hash. You’ll utilise the exec method available by extending to the QorusJobTest class:
exec(action);
Hash result = action.getJobResult();

6. Check That A Workflow Order Is Created

Get the job info with:
Hash[] jinfo = (Hash[])getJobResultHash(result.getLong("job_instanceid")).get("info");
Now, check if one workflow order is created:
assertEq(1, jinfo.length, "check wf orders created");

7. Wait For Order Status

Wait for the order and get its status with:
void waitForStatus(long wfiid) throws Throwable { 
   waitForStatus(wfiid, $Constants.StatComplete);
}

void waitForStatus(long wfiid, String status) throws Throwable {
   String stat;
   // poll the status ~4 times a second
   while (true) {
      Hash h = (Hash)qrest.get("orders/" + String.valueOf(wfiid));
      stat = h.getString("workflowstatus");
      if (stat.equals(status) || stat.equals($Constants.StatError)) {
         break;
      }
         // wait for status to change
         Thread.sleep(250);
   }
   // output the status
   System.out.println(String.format("workflow order ID %d has status %s", wfiid, stat));
   // assert that the status is the desired status
   assertEq(stat, status, "wfiid " + String.valueOf(wfiid) + " has status " + status);
}
Call the above waitForStatus method like so:
waitForStatus(jinfo[0].getLong("workflow_instanceid"));

8. Get Workflow Order Info And Assert That It's COMPLETE

You can get the workflow order and assert that it’s complete by:
// get workflow order info
long wfiid = jinfo[0].getLong("workflow_instanceid");
Hash winfo = (Hash)qrest.get("orders/" + String.valueOf(wfiid));
// assert that it's COMPLETE
assertEq($Constants.StatComplete, winfo.getString("workflowstatus"), "check order status");

9. Check If The Test Data Has Been Added To The Table

Check if the test data is added to the table, since we generated the CSV data with five records, we check if five new rows are added to the table:
assertEq(num_recs + 5, inventory_example.rowCount(), "check data imported in DB");

10. Check For Duplicate File Handling

Let’s resubmit the same file and repeat the process to test for duplicate file handling
putFileOnSftpServer(filename, csv);
// create a "RunJobResult" action and test for a COMPLETE status
action = new RunJobResult($Constants.StatComplete);
// run the job and check the result
exec(action);
result = action.getJobResult();
// check job results that no new workflow order was created
Hash[] jinfo2 = (Hash[])getJobResultHash(result.getLong("job_instanceid")).get("info");
assertEq(1, jinfo2.length, "check duplicate job result info length");
assertEq(wfiid, jinfo2[0].getLong("workflow_instanceid"),
   "check duplicate wf order ID");
assertTrue(jinfo2[0].getBool("duplicate"), "verify duplicate flag");

11. Reset Job To Run Normally After The Test Completes

Reset the job to run normally after the test completes with:
qrest.put("jobs/example-import-csv-file/setActive", new Hash() {
   {
      put("active", true);
   }
});

Running The Test

Go to the Qorus IDE and in the interface hierarchy view, under the Tests file type, find the ExampleImportCsvFileTest.java (or the name of your test script ) test. Run the test by clicking on the play icon next to the test’s name.

You should see the following output after a successful test in VS Code at View → Output → Qorus Remote Development

   ... response: wrote 598 bytes of StockReport-ecd22aa9-bdc1-4bbc-bb9a-1c4b1baa8b02.csv to sftp://[your-server:port]
workflow order ID 4 has status COMPLETE
wrote 598 bytes of StockReport-ecd22aa9-bdc1-4bbc-bb9a-1c4b1baa8b02.csv to sftp://[your-server:port]
QUnit Test "example-import-csv-file" v<latest>
Ran 1 test case, 1 succeeded (13 assertions)
... status: FINISHED
... response: OpenJDK 64-Bit Server VM warning: Archived non-system classes are disabled because the java.system.class.loader property is specified (value = "org.qore.jni.QoreURLClassLoader"). To use archived non-system classes, this property must be not be set
... status: FINISHED
Test execution finished successfully (ID: 21)

Conclusion

In this blog post, you explored the steps involved in testing the functionality developed in the Poll SFTP Server For CSV Data And Import To DB blog post series using Java. The complete script is available in the examples/csv-sftp-to-db-import/ExampleImportCsvFileTest.java of the Qorus’ building blocks repo.

Grab your FREE application integrations eBook.
You'll quickly learn how to connect your apps and data into robust processes!