◆ Problem
You have noticed a defect pattern when deploying any new schema object: the first user that tries it tells you that it does not work.
◆ Background
We received this e-mail from Carl Manaster, describing the defect pattern in only sixty words.
“I write a stored procedure in the development database, test it, and call it good. I copy it into the production database, test it there, find it still good, and release it. But I never granted regular users execute permission on it, so I get a call from the first user to try it, telling me it doesn’t work.”
353 Test permissions when
deploying schema objects
If you have this pattern,18 then you need more tests, and this recipe describes the test you need.
◆ Recipe
It is common to forget to grant permission for users to actually use a schema object, such as a stored procedure or table. Unfortunately, the SQL standard does not provide a way to ask a database object whether a given user has sufficient authority to perform an operation on that object. As a result, your test should just try to use the object and fail only if there is a permissions problem. One question:
how does your database vendor report permission problems?
In order to find that out, start with a Learning Test. Create a stored procedure (Carl’s particular problem), revoke your privilege to execute it, and then try to execute it. Let the JDBC provider show you how it reports a permission problem.
Listing 10.13 shows the test.
public class StoredProcedurePrivilegesTest extends CoffeeShopDatabaseFixture { protected void setUp() throws Exception { super.setUp();
Statement statement = getConnection().createStatement();
registerStatement(statement);
try {
statement.executeUpdate("drop procedure NOT_ALLOWED");
}
catch (SQLException doesNotExist) {
if ("42000".equals(doesNotExist.getSQLState()) == false) { throw doesNotExist;
} }
statement.executeUpdate(
"create procedure NOT_ALLOWED() begin end");
}
protected void tearDown() throws Exception {
Statement statement = getConnection().createStatement();
registerStatement(statement);
statement.executeUpdate("drop procedure NOT_ALLOWED");
super.tearDown();
}
18Geek shorthand for “If you have noticed that this pattern also applies to you...”
Listing 10.13 Testing privileges on stored procedures
354 CHAPTER 10 Testing and JDBC
public void testSeePermissionProblem() throws Exception { Connection connection =
getDataSource().getConnection("programmer", "pr0grammer");
Statement statement = connection.createStatement();
registerStatement(statement);
statement.execute("call NOT_ALLOWED()");
} }
We executed this test and received the message java.sql.SQLException: The procedure NOT_ALLOWED does not exist (or no execute privilege), which told us to catch an SQLException and look at the error code and SQL state. We added that to the test, which you can see in listing 10.14.
public void testSeePermissionProblem() throws Exception { Connection connection =
getDataSource().getConnection("programmer", "pr0grammer");
Statement statement = connection.createStatement();
registerStatement(statement);
try {
statement.execute("call NOT_ALLOWED()");
fail("User 'programmer' allowed to call NOT_ALLOWED?!");
}
catch (SQLException e) {
assertEquals(0, e.getErrorCode());
assertEquals("", e.getSQLState());
} }
We are quite certain that the assertions we have just added will fail, but once they do we can replace the expected values with the correct ones. This is a miniature version of the Gold Master technique. After discovering the two values—both of which are highly vendor dependent, so do not copy these into your code—we fixed the catch block of the test.
try {
statement.execute("call NOT_ALLOWED()");
fail("User 'programmer' allowed to call NOT_ALLOWED?!");
}
catch (SQLException e) {
assertEquals(-12743, e.getErrorCode());
assertEquals("42000", e.getSQLState());
}
Listing 10.14 Adding code to expect an SQLException
Should throw SQLException
Change expected values after we see them
355 Test permissions when
deploying schema objects
Now we have a test that fails if a user attempts to call the specified stored proce- dure and they have permission. We did this to learn how Mimer (in this case) reports permission problems. Now we can write the test we really want, which uses this information to distinguish a test failure from the test “blowing up.”
Listing 10.15 shows a test that verifies a user has permission to call a stored procedure.
package junit.cookbook.coffee.jdbc.test;
import java.sql.*;
public class StoredProcedurePrivilegesTest extends CoffeeShopDatabaseFixture { // setUp and tearDown omitted
public void testCanCall() throws Exception { Connection connection =
getDataSource().getConnection("programmer", "pr0grammer");
Statement statement = connection.createStatement();
registerStatement(statement);
try {
statement.execute("call NOT_ALLOWED()");
}
catch (SQLException e) {
if (isNoPrivilegesException(e))
fail("User 'programmer' cannot call procedure "
+ "NOT_ALLOWED");
else
throw e;
}
finally {
connection.close();
} }
private boolean isNoPrivilegesException(SQLException e) { return (-12743 == e.getErrorCode()) && ("42000".equals(e.getSQLState()));
} }
This is one of those rare times that we decide to catch an unexpected excep- tion—well, a reasonably unexpected one—and fail, rather than let the exception be propagated up to the JUnit framework. This is really a question of taste: in this case we would rather report simply that the user does not have the expected
Listing 10.15 StoredProcedurePrivilegesTest
Signal “unexpected exception”
Move to reusable library
356 CHAPTER 10 Testing and JDBC
permission. We could rely on Mimer’s error message to tell us that, but this way if Mimer changes, our message remains as informative as it ever was.
The next step is to extract the bare test “engine” from this test and execute it for all the stored procedures and against all the users you expect to have permis- sion. The input to the test consists of a user name, a stored procedure, and your expectation regarding their authority to execute it. You can see a short example access control list in table 10.1. This is the data for your tests.
Now that you have tabular data you can create a Parameterized Test Case (see rec- ipe 4.8, “Build a data-driven test suite”) where the data for each test is a row in this table. Externalize the tabular data, such as to a file, in order to make it easy to keep up to date alongside your evolving list of stored procedures.
◆ Discussion
The one large stumbling block in this recipe is that management may strictly for- bid you from even attempting to execute these tests on the production server, which is where you need to test them most! Frankly, we have no idea how to help you here, because we still have much to learn about negotiating effectively. The best you can do is to ask very nicely and emphasize the number of support calls that these tests will save. Of course, if executing the test runs any risk whatsoever of harming the database, then they are right to not let you execute it. This is a case where it may be necessary to test the tests.
Although our example here was testing permissions on a stored procedure, remember that you should test permissions on all your schema objects, not just stored procedures.
◆ Related
■ 10.12—Test stored procedures
Table 10.1 Sample access control list for stored procedures
User Description Stored procedure Allowed to execute?
admin Administrator addProduct Yes
csr Customer service representative addProduct No
clerk Data entry clerk addProduct No
marketing Marketing professional addProduct Yes
357 Test legacy JDBC code
without the database