Usage
With the Scribble JCR Rules, you can create test cases that may interact with a standards conforming JCR repository. Scribble provides a set of various JCR repository implementations to support various use cases.
All repository rule implementation rely on the Jackrabbit 2 reference implementation of the JSR 283. Although there is a successor implementation Apache OAK available, the decision to use Jackrabbit 2 was driven mainly, because it provides a complete implementation of the specification, including the optional aspects - which might be relevant when writing test cases. OAK aims at scalability and performance at the cost of leaving out some optional elements (i.e. support for same-name sibblings), and scalability and performance might be less important for unit testing.
Repository Rules
All the repository rules inherit from io.inkstand.scribble.rules.jcr.ContentRepository
that provides the following default functions
getRepository()
access to the javax.jcr.Repository instance managed by the rule.login(username, password)
login to the underlying repository using the given credentials to obtain a javax.jcr.Sessionlogin(username, password)
login to the underlying repository using the given credentials to obtain a javax.jcr.SessiongetAdminSession()
login to the underlying repository to obtain a javax.jcr.Session with administrator privileges. The session is reused and refreshed on every call of the method. The method uses the default credentials "admin"/"admin".getWorkingDirectory()
access to the outer rule TemporaryFolder that provides access to the working directory in the file systemgetInjectionValue()
provides Injection support by returning the javax.jcr.Repository to be injected into a target object
io.inkstand.scribble.jcr.rules.InMemoryContentRepository
The in-memory content repository rule creates a pre-configured Repository that has in-memory persistence and filestore. All of it�s contents is gone once the repository is shut-down. The in-memory repository is using the TransientRepository of Jackrabbit that automatically shuts down the repository when the last session has been closed.
The in-memory repository requires a working directory, so you need to create it in a TemporaryFolder.
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newTempFolder().aroundInMemoryContentRepository().build();
|
The default configuration has no effective security enabled as it uses Jackrabbit's SimpleSecurityManager
io.inkstand.scribble.jcr.rules.StandaloneContentRepository
The StandaloneContentRepository offers the same persistance as a real JCR repository. It creates the file structure for the repository inside a temporary folder and starts up the repository.Unline the In-Memory Repository, this repository is not shut down when the last session is closed. The default configuration uses the same in-memory persistence configuration as the InMemoryContentRepository, but it can be changed to use a custom configuration with configured persistence so that you can start and shutdown the repository during the test without loosing any data stored.
To create a standalone repository with the default configuration use the following scribble
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newTempFolder()
.aroundStandaloneContentRepository()
.build();
|
To create a standalone repository with a custom configuration accessible through an URL use the following scribble
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newTempFolder()
.aroundStandaloneContentRepository()
.withConfiguration(configURL)
.build();
|
The default configuration has no effective security enabled as it uses Jackrabbit's SimpleSecurityManager
io.inkstand.scribble.jcr.rules.JNDIContentRepository (experimental)
The JNDI Content Repository allows to provide access to the repository using a JNDI context lookup. The rule allows to use an existing context, set up the context with properties and define a lookup name.
The default lookup name for the JCR repository is java:/jcr/local
.
The use case for this rule is to test inside an integrated environment such as an Application Server, where a pre-configured repository is accessible through Java naming lookups, i.e. when doing integration testing with Arquillian.
Default Lookup
To create a test rule for a content repository available through the default lookup name and using the default InitialContext make the following scribble:
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newJNDIContentRepository().build();
|
Custom Lookup name
To create a test rule for a content repository available through a custom lookup name and using the default InitialContext make the following scribble:
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newJNDIContentRepository()
.withLookup("java:/custom/lookup/name")
.build();
|
Context handling
To use an existing context
No Format |
---|
private Context context;
@Rule
public ContentRepository repository = Scribble.newJNDIContentRepository()
.usingContext(context)
.build();
|
To use a specific context factory
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newJNDIContentRepository()
.withInitialContextFactory("some.package.Factory")
.build();
|
or
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newJNDIContentRepository()
.withInitialContextFactory(some.package.Factory.class)
.build();
|
To implicitly create an initial context factory using custom properties
No Format |
---|
private Properties properties = ...;
public ContentRepository repository = Scribble.newJNDIContentRepository()
.withContextProperties(properties)
.build();
|
or
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newJNDIContentRepository()
.withContextProperty("aName", aValue)
.withContextProperty("bName", bValue)
.build();
|
And of course you can combine everything
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newJNDIContentRepository()
.withInitialContextFactory(some.package.Factory.class)
.withContextProperty("aName", aValue)
.withContextProperty("bName", bValue)
.withLookup("java:/custom/lookup/name")
.build();
|
Remote Naming
The JNDI Content Repository builder supports to specify a remote naming provider and a principal for accessing the remote naming services
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newJNDIContentRepository()
.withProviderURL(someURL)
.withSecurityPrincipal("principalName", "credentials")
.build();
|
A more concrete example for JBoss' remote naming service would be
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newJNDIContentRepository()
.withProviderURL("remote://localhost:4447")
.withSecurityPrincipal("remote", "secret")
.build();
|
There are some limitations to remote naming. Remote naming is only supported if the transferred objects are serializable. Which is not the case when using the Jackrabbit JCA connector which does not support remote naming!
So consider the remote naming support of this test rule to be experimental only!
io.inkstand.scribble.jcr.rules.MockContentRepository
The mock content repository simply provides access to a mock repository created using Mockito. The repository has to be set up manually using the known when...thenReturn or doReturn().when()... statements.
The mock repository does not require a working directory so it can be created directly from Scribble's factory method
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newMockContentRepository().build();
|
User Management
User mangement of JCR repositories is not mandated by the JCR spec and is therefore up to the JCR implementation. The base rule for all Scribble ContentRepositories defines the methods for adding and removing a user. However, only the implementation specific rules such as InMemoryContentRepository and StandaloneContentRepository have these methods implemented as they both rely on the reference implementation Jackrabbit. All other ContentRepository rules will throw an UnsupportedOperationException.
Adding a user
To add a user to the repositories user management, invoke
No Format |
---|
Principal user = repository.addUser("username", "password");
|
The returned principal reflects the created user.
Removing a user
To remove an existing user, invoke
boolean success = repository.removeUser("username");
The method will return true if the user was found and could be deleted and false if no such user was found. The method will fail with an AssertionError if the delete operation failed for any repository-internal reason.
Resetting users
In case you're using the repository as a class rule, you might want to reset the created users after each test. The rule implementation keep track of the created uses so a call to resetUsers()will remove all users created using the test rule's methods.
No Format |
---|
repository.resetUsers();
|
h2.Access Control
The ContentRepository rule provides methods for user management and access control. Some of these methods are specifc to the JCR implementation and are thefore not implemented (throwing UnsupportedOperationException). Only those methods, that rely on pure JCR API are implemented.
Granting privileges
To grant a user a specific permission, you have to invoke
No Format |
---|
repository.grant("userid", "/path/to/node", "privilege");
|
You may pass multiple privileges, i.e. jcr:read , jcr:write, the parameterer is vararg String. An complete list of all the privileges defined in the JCR specification can be found here JCR: Access Control Management.
The method uses only JCR API calls and is therefore available for all repositories.
Denying privileges
Denying a privilege is the opposite of granting. A user may be explicitly denied to perform a certain action on a node, although the privilege is granted on one of the parent nodes. The syntax is similar to granting a permission:
No Format |
---|
repository.deny("userid", "/path/to/node", "privilege");
|
The deny() operation is only available on the InMemoryContentRepository and the StandaloneContentRepository.
Resetting Access Control List
To remove all access control list (ACL) entries on a single node for either a single user, multiple users or all users, the repository rule provides the clearACL() method.
To clear the ACLs for a single user, invoke:
No Format |
---|
repository.clearACL("/path/to/node", "userId");
|
To clear the ACLs for a multiple users, invoke:
No Format |
---|
repository.clearACL("/path/to/node", "user1", "user2", "user3",...);
|
To clear the ACLs for all users, invoke
No Format |
---|
repository.clearACL("/path/to/node");
|
A word of warning, removing the ACLs on the root node "/" for all users may render the repository useless as even the administrator ACLs get removed. Use with caution in conjunction with standalone repositories with real persistence.
Utility Rules
io.inkstand.scribble.jcr.rules.ActiveSession
With the active session you can automatically log in to a repository during test setup so you have an authenticated session easily available. The ActiveSession can be nested inside any of the Repository rule
No Format |
---|
@Rule
public ContentRepository repository = Scribble.newTempFolder()
.aroundInMemoryContentRepository()
.aroundSession("username", "password")
.build();
|
Note that when you're using the In-Memory Content Repository, logging out the active session will shut-down the repository!
io.inkstand.scribble.jcr.rules.ContentLoader
see here The PDF module contains matchers for verifying PDF documents which are the result of a PDF processing. The module provides a wrapper for a PDF source that hides the underlying PDF processing library (which is Apache PDFBox at the moment). The matchers provided are rather limited at the moment, but will increase in the future.
Examples
Source of a PDF can be one of the following and is referred to as source
in the following examples - byte[] - java.io.InputStream - java.io.File - java.nio.file.Path - java.net.URL - java.lang.String
Code Block | ||||
---|---|---|---|---|
| ||||
import io.inkstand.scribble.pdf.PDF;
import static io.inkstand.scribble.pdf.PDFMatchers.*;
|
No Format |
---|
assertThat(PDF.of(source), isPDF());
|
No Format |
---|
assertThat(PDF.of(source), hasPages(3));
|
No Format |
---|
assertThat(PDF.of(source), conformsTo(PDFALevel.PDFA_1B));
|