Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion ecosystem/testcontainers-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,29 @@ To run the tests for the application locally, follow these steps:

2. Make sure you have the appropriate Java version installed.

3. Execute the following command:
3. Make sure [Docker](https://www.docker.com/products/docker-desktop/) is installed and running. The integration
tests (`*IT.java`) use [Testcontainers](https://testcontainers.com/) to start a real Payara Micro instance with
the application deployed, so Docker needs to be reachable from the JVM running the tests. If you hit
`Could not find a valid Docker environment` on Windows even though `docker` works fine from the shell, see
[Testcontainers' Windows troubleshooting guide](https://java.testcontainers.org/supported_docker_environment/windows/).

4. Execute the following command:

```
./mvn clean verify
```

### What gets tested

- `*ServiceIT` (`fish.payara.examples.service`) — REST API tests hitting the deployed application directly with a
JAX-RS client.
- `*UiIT` (`fish.payara.examples.ui`) — browser tests driving the JSF pages (Book, Patron, Librarian, Loan) end to
end with [Playwright](https://playwright.dev/java/). The Playwright Chromium browser binary is installed
automatically as part of the build (see the `exec-maven-plugin` execution in `pom.xml`) — no separate setup step
is needed.

Both sets of IT tests share a single Payara Micro Testcontainer for the whole test run (see
`fish.payara.examples.testcontainers.AbstractContainerIT`), so it's started once instead of once per test class.



33 changes: 32 additions & 1 deletion ecosystem/testcontainers-example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.21.1</version>
<version>1.21.4</version>
<scope>test</scope>
<type>jar</type>
</dependency>
Expand Down Expand Up @@ -118,6 +118,35 @@
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<!-- Installs the Playwright browser binaries (used by the *UiIT tests) before the
integration-test phase runs. Uses exec:exec (a real child process) rather than
exec:java, because com.microsoft.playwright.CLI calls System.exit() internally,
which would otherwise kill the whole Maven JVM. -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.3</version>
<executions>
<execution>
<id>install-playwright-browsers</id>
<phase>generate-test-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<classpathScope>test</classpathScope>
<arguments>
<argument>-classpath</argument>
<classpath/>
<argument>com.microsoft.playwright.CLI</argument>
<argument>install</argument>
<argument>chromium</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<!-- Execute 'mvn clean package payara-micro:dev' to run the application. -->
<plugin>
<groupId>fish.payara.maven.plugins</groupId>
Expand Down Expand Up @@ -201,6 +230,8 @@
</includes>
<systemPropertyVariables>
<payara.microJar>${project.build.directory}/payara-micro-${payara.version}.jar</payara.microJar>
<payara.version>${payara.version}</payara.version>
<war.path>${project.build.directory}/${project.build.finalName}.war</war.path>
</systemPropertyVariables>
</configuration>
</execution>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Objects;

import java.util.List;
import java.util.UUID;
import jakarta.json.bind.annotation.JsonbTransient;

@NamedQueries({
Expand All @@ -15,10 +16,23 @@
@Entity
public class Book {

// Plain @Id, no @GeneratedValue, with an isBlank() check in @PrePersist:
// see Librarian.assignId() for the full explanation - book.xhtml's
// "Isbn:" field is a plain h:inputText bound directly to isbn, so saving
// it blank submits "" (not null) for this String property, which needs
// the same isBlank() guard to avoid persisting a literal empty-string id.
// Note: this is a generated surrogate key, not a real ISBN - the field
// name predates this fix.
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private String isbn;

@PrePersist
private void assignId() {
if (isbn == null || isbn.isBlank()) {
isbn = UUID.randomUUID().toString();
}
}

private String title;

private String author;
Expand All @@ -30,6 +44,33 @@ public class Book {
private List<Loan> loans;


@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + Objects.hashCode(this.isbn);
return hash;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Book other = (Book) obj;
return Objects.equals(this.isbn, other.isbn);
}

@Override
public String toString() {
return String.valueOf(title);
}

// Getters and setters

public String getIsbn() {
Expand Down Expand Up @@ -72,31 +113,4 @@ public void setLoans(List<Loan> loans) {
this.loans = loans;
}

@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + Objects.hashCode(this.isbn);
return hash;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Book other = (Book) obj;
return Objects.equals(this.isbn, other.isbn);
}

@Override
public String toString() {
return String.valueOf(title);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Objects;

import java.util.List;
import java.util.UUID;
import jakarta.json.bind.annotation.JsonbTransient;

@NamedQueries({
Expand All @@ -14,10 +15,33 @@
@Entity
public class Librarian {

// Plain @Id, no @GeneratedValue: JPA's generation strategies (IDENTITY/
// SEQUENCE/TABLE/AUTO) target numeric surrogate keys; for a String @Id
// we assign it ourselves in @PrePersist instead. Checking isBlank() (not
// just null) matters: librarian.xhtml's "Librarian ID:" field is a plain
// h:inputText bound directly to this property, and unlike a numeric
// property (where JSF's implicit converter turns an empty submission into
// null), a String property gets the submitted value verbatim - so saving
// the form with that field left blank sets librarianID to "" rather than
// null. That empty string is not null, so it silently passed the create/
// edit check as "already has an id" and, before this fix, also passed
// this method's null check unchanged - persisting the row with a literal
// empty-string id. It looked fine in this page's own list (name/actions
// still worked), but broke anything that keyed off "does this entity have
// an id yet": the loan.xhtml dropdown's converter treats "" the same as
// "no id" and renders it as an empty <option value="">, indistinguishable
// from the "no selection" placeholder - so selecting that librarian for a
// loan silently saved as no librarian at all.
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private String librarianID;

@PrePersist
private void assignId() {
if (librarianID == null || librarianID.isBlank()) {
librarianID = UUID.randomUUID().toString();
}
}

private String name;

private String department;
Expand All @@ -27,6 +51,33 @@ public class Librarian {
private List<Loan> loansManageds;


@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + Objects.hashCode(this.librarianID);
return hash;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Librarian other = (Librarian) obj;
return Objects.equals(this.librarianID, other.librarianID);
}

@Override
public String toString() {
return String.valueOf(name);
}

// Getters and setters

public String getLibrarianID() {
Expand Down Expand Up @@ -61,31 +112,4 @@ public void setLoansManageds(List<Loan> loansManageds) {
this.loansManageds = loansManageds;
}

@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + Objects.hashCode(this.librarianID);
return hash;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Librarian other = (Librarian) obj;
return Objects.equals(this.librarianID, other.librarianID);
}

@Override
public String toString() {
return String.valueOf(name);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@
@Entity
public class Loan {

// GenerationType.TABLE (not AUTO or IDENTITY): EclipseLink's AUTO resolution for a
// numeric @Id on H2 still picks the platform's native IDENTITY column generation,
// which produces "INTEGER IDENTITY NOT NULL" DDL that this H2 version's parser
// rejects (a real schema-creation failure, not a test issue - see the container
// logs: CREATE TABLE LOAN fails, so the table never exists). TABLE forces the same
// EclipseLink default table-based generator (the SEQUENCE table) that Book/Patron/
// Librarian's AUTO-strategy String ids already use successfully.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.TABLE)
private Integer loanID;

private LocalDateTime loanDate;
Expand All @@ -34,6 +41,33 @@ public class Loan {
private Book book;


@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + Objects.hashCode(this.loanID);
return hash;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Loan other = (Loan) obj;
return Objects.equals(this.loanID, other.loanID);
}

@Override
public String toString() {
return String.valueOf(loanID);
}

// Getters and setters

public Integer getLoanID() {
Expand Down Expand Up @@ -84,31 +118,4 @@ public void setBook(Book book) {
this.book = book;
}

@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + Objects.hashCode(this.loanID);
return hash;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Loan other = (Loan) obj;
return Objects.equals(this.loanID, other.loanID);
}

@Override
public String toString() {
return String.valueOf(loanID);
}

}
Loading