Commit 54cb7b1d authored by sumet's avatar sumet

Add ebean

parent 983bd3f9
logs
project/project
project/target
target
tmp
.history
dist
/.idea
/*.iml
/out
/.idea_modules
.classpath
.project
/RUNNING_PID
.settings
.target
.cache
bin
.DS_Store
activator-sbt-*-shim.sbt
dist: trusty
sudo: true
group: beta
language: scala
scala:
- 2.11.11
- 2.12.3
jdk:
- oraclejdk8
cache:
directories:
- "$HOME/.ivy2/cache"
- "$HOME/.sbt/launchers"
before_cache:
- rm -rf $HOME/.ivy2/cache/com.typesafe.play/*
- rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/*
- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm
notifications:
slack:
secure: K6HWTI6zJpQfxS7sH5ZQ1jEK5TkkUl5GtcGinNecHMBqvfS4IXAnU23lz/kLqCqMVPIFaRx1g6UwgJgMvR4XWeIhpzLOzAnOOcmv+kQzv7A8vEJBM20z1HNzDcxzvuNNO2BHn8EjXh5VD65vXMcA+lKzUxASey/Rs+CBReQWE7M=
License
-------
Written in 2016 by Lightbend <info@lightbend.com>
To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
[<img src="https://img.shields.io/travis/playframework/play-java-ebean-example.svg"/>](https://travis-ci.org/playframework/play-java-ebean-example)
# play-java-ebean-example
This is an example Play application that uses Java, and communicates with an in memory database using EBean.
The Github location for this project is:
[https://github.com/playframework/play-java-ebean-example](https://github.com/playframework/play-java-ebean-example)
## Play
Play documentation is here:
[https://playframework.com/documentation/latest/Home](https://playframework.com/documentation/latest/Home)
## EBean
EBean is a Java ORM library that uses SQL:
[https://www.playframework.com/documentation/latest/JavaEbean](https://www.playframework.com/documentation/latest/JavaEbean)
and the documentation can be found here:
[https://ebean-orm.github.io/](https://ebean-orm.github.io/)
package controllers;
import models.Computer;
import play.data.Form;
import play.data.FormFactory;
import play.libs.concurrent.HttpExecutionContext;
import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.Results;
import repository.CompanyRepository;
import repository.ComputerRepository;
import javax.inject.Inject;
import javax.persistence.PersistenceException;
import java.util.Map;
import java.util.concurrent.CompletionStage;
/**
* Manage a database of computers
*/
public class HomeController extends Controller {
private final ComputerRepository computerRepository;
private final CompanyRepository companyRepository;
private final FormFactory formFactory;
private final HttpExecutionContext httpExecutionContext;
@Inject
public HomeController(FormFactory formFactory,
ComputerRepository computerRepository,
CompanyRepository companyRepository,
HttpExecutionContext httpExecutionContext) {
this.computerRepository = computerRepository;
this.formFactory = formFactory;
this.companyRepository = companyRepository;
this.httpExecutionContext = httpExecutionContext;
}
/**
* This result directly redirect to application home.
*/
private Result GO_HOME = Results.redirect(
routes.HomeController.list(0, "name", "asc", "")
);
/**
* Handle default path requests, redirect to computers list
*/
public Result index() {
return GO_HOME;
}
/**
* Display the paginated list of computers.
*
* @param page Current page number (starts from 0)
* @param sortBy Column to be sorted
* @param order Sort order (either asc or desc)
* @param filter Filter applied on computer names
*/
public CompletionStage<Result> list(int page, String sortBy, String order, String filter) {
// Run a db operation in another thread (using DatabaseExecutionContext)
return computerRepository.page(page, 10, sortBy, order, filter).thenApplyAsync(list -> {
// This is the HTTP rendering thread context
return ok(views.html.list.render(list, sortBy, order, filter));
}, httpExecutionContext.current());
}
/**
* Display the 'edit form' of a existing Computer.
*
* @param id Id of the computer to edit
*/
public CompletionStage<Result> edit(Long id) {
// Run a db operation in another thread (using DatabaseExecutionContext)
CompletionStage<Map<String, String>> companiesFuture = companyRepository.options();
// Run the lookup also in another thread, then combine the results:
return computerRepository.lookup(id).thenCombineAsync(companiesFuture, (computerOptional, companies) -> {
// This is the HTTP rendering thread context
Computer c = computerOptional.get();
Form<Computer> computerForm = formFactory.form(Computer.class).fill(c);
return ok(views.html.editForm.render(id, computerForm, companies));
}, httpExecutionContext.current());
}
/**
* Handle the 'edit form' submission
*
* @param id Id of the computer to edit
*/
public CompletionStage<Result> update(Long id) throws PersistenceException {
Form<Computer> computerForm = formFactory.form(Computer.class).bindFromRequest();
if (computerForm.hasErrors()) {
// Run companies db operation and then render the failure case
return companyRepository.options().thenApplyAsync(companies -> {
// This is the HTTP rendering thread context
return badRequest(views.html.editForm.render(id, computerForm, companies));
}, httpExecutionContext.current());
} else {
Computer newComputerData = computerForm.get();
// Run update operation and then flash and then redirect
return computerRepository.update(id, newComputerData).thenApplyAsync(data -> {
// This is the HTTP rendering thread context
flash("success", "Computer " + newComputerData.name + " has been updated");
return GO_HOME;
}, httpExecutionContext.current());
}
}
/**
* Display the 'new computer form'.
*/
public CompletionStage<Result> create() {
Form<Computer> computerForm = formFactory.form(Computer.class);
// Run companies db operation and then render the form
return companyRepository.options().thenApplyAsync((Map<String, String> companies) -> {
// This is the HTTP rendering thread context
return ok(views.html.createForm.render(computerForm, companies));
}, httpExecutionContext.current());
}
/**
* Handle the 'new computer form' submission
*/
public CompletionStage<Result> save() {
Form<Computer> computerForm = formFactory.form(Computer.class).bindFromRequest();
if (computerForm.hasErrors()) {
// Run companies db operation and then render the form
return companyRepository.options().thenApplyAsync(companies -> {
// This is the HTTP rendering thread context
return badRequest(views.html.createForm.render(computerForm, companies));
}, httpExecutionContext.current());
}
Computer computer = computerForm.get();
// Run insert db operation, then redirect
return computerRepository.insert(computer).thenApplyAsync(data -> {
// This is the HTTP rendering thread context
flash("success", "Computer " + computer.name + " has been created");
return GO_HOME;
}, httpExecutionContext.current());
}
/**
* Handle computer deletion
*/
public CompletionStage<Result> delete(Long id) {
// Run delete db operation, then redirect
return computerRepository.delete(id).thenApplyAsync(v -> {
// This is the HTTP rendering thread context
flash("success", "Computer has been deleted");
return GO_HOME;
}, httpExecutionContext.current());
}
}
package models;
import io.ebean.Model;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
public class BaseModel extends Model {
@Id
public Long id;
}
package models;
import play.data.validation.Constraints;
import javax.persistence.Entity;
/**
* Company entity managed by Ebean
*/
@Entity
public class Company extends BaseModel {
private static final long serialVersionUID = 1L;
@Constraints.Required
public String name;
}
package models;
import play.data.format.Formats;
import play.data.validation.Constraints;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import java.util.Date;
/**
* Computer entity managed by Ebean
*/
@Entity
public class Computer extends BaseModel {
private static final long serialVersionUID = 1L;
@Constraints.Required
public String name;
@Formats.DateTime(pattern="yyyy-MM-dd")
public Date introduced;
@Formats.DateTime(pattern="yyyy-MM-dd")
public Date discontinued;
@ManyToOne
public Company company;
}
package repository;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import models.Company;
import play.db.ebean.EbeanConfig;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletionStage;
import static java.util.concurrent.CompletableFuture.supplyAsync;
/**
*
*/
public class CompanyRepository {
private final EbeanServer ebeanServer;
private final DatabaseExecutionContext executionContext;
@Inject
public CompanyRepository(EbeanConfig ebeanConfig, DatabaseExecutionContext executionContext) {
this.ebeanServer = Ebean.getServer(ebeanConfig.defaultServer());
this.executionContext = executionContext;
}
public CompletionStage<Map<String, String>> options() {
return supplyAsync(() -> ebeanServer.find(Company.class).orderBy("name").findList(), executionContext)
.thenApply(list -> {
HashMap<String, String> options = new LinkedHashMap<String, String>();
for (Company c : list) {
options.put(c.id.toString(), c.name);
}
return options;
});
}
}
package repository;
import io.ebean.*;
import models.Computer;
import play.db.ebean.EbeanConfig;
import javax.inject.Inject;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import static java.util.concurrent.CompletableFuture.supplyAsync;
/**
* A repository that executes database operations in a different
* execution context.
*/
public class ComputerRepository {
private final EbeanServer ebeanServer;
private final DatabaseExecutionContext executionContext;
@Inject
public ComputerRepository(EbeanConfig ebeanConfig, DatabaseExecutionContext executionContext) {
this.ebeanServer = Ebean.getServer(ebeanConfig.defaultServer());
this.executionContext = executionContext;
}
/**
* Return a paged list of computer
*
* @param page Page to display
* @param pageSize Number of computers per page
* @param sortBy Computer property used for sorting
* @param order Sort order (either or asc or desc)
* @param filter Filter applied on the name column
*/
public CompletionStage<PagedList<Computer>> page(int page, int pageSize, String sortBy, String order, String filter) {
return supplyAsync(() ->
ebeanServer.find(Computer.class).where()
.ilike("name", "%" + filter + "%")
.orderBy(sortBy + " " + order)
.fetch("company")
.setFirstRow(page * pageSize)
.setMaxRows(pageSize)
.findPagedList(), executionContext);
}
public CompletionStage<Optional<Computer>> lookup(Long id) {
return supplyAsync(() -> Optional.ofNullable(ebeanServer.find(Computer.class).setId(id).findOne()), executionContext);
}
public CompletionStage<Optional<Long>> update(Long id, Computer newComputerData) {
return supplyAsync(() -> {
Transaction txn = ebeanServer.beginTransaction();
Optional<Long> value = Optional.empty();
try {
Computer savedComputer = ebeanServer.find(Computer.class).setId(id).findOne();
if (savedComputer != null) {
savedComputer.company = newComputerData.company;
savedComputer.discontinued = newComputerData.discontinued;
savedComputer.introduced = newComputerData.introduced;
savedComputer.name = newComputerData.name;
savedComputer.update();
txn.commit();
value = Optional.of(id);
}
} finally {
txn.end();
}
return value;
}, executionContext);
}
public CompletionStage<Optional<Long>> delete(Long id) {
return supplyAsync(() -> {
try {
final Optional<Computer> computerOptional = Optional.ofNullable(ebeanServer.find(Computer.class).setId(id).findOne());
computerOptional.ifPresent(Model::delete);
return computerOptional.map(c -> c.id);
} catch (Exception e) {
return Optional.empty();
}
}, executionContext);
}
public CompletionStage<Long> insert(Computer computer) {
return supplyAsync(() -> {
computer.id = System.currentTimeMillis(); // not ideal, but it works
ebeanServer.insert(computer);
return computer.id;
}, executionContext);
}
}
package repository;
import akka.actor.ActorSystem;
import play.libs.concurrent.CustomExecutionContext;
import javax.inject.Inject;
/**
* Custom execution context, so that blocking database operations don't
* happen on the rendering thread pool.
*
* @link https://www.playframework.com/documentation/latest/ThreadPools
*/
public class DatabaseExecutionContext extends CustomExecutionContext {
@Inject
public DatabaseExecutionContext(ActorSystem actorSystem) {
super(actorSystem, "database.dispatcher");
}
}
@(computerForm: Form[Computer], companies: Map[String, String])
@import helper._
@main {
<h1>Add a computer</h1>
@form(routes.HomeController.save()) {
<fieldset>
@CSRF.formField
@inputText(computerForm("name"), '_label -> "Computer name", '_help -> "")
@inputText(computerForm("introduced"), '_label -> "Introduced date", '_help -> "")
@inputText(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "")
@select(
computerForm("company.id"),
options(companies),
'_label -> "Company", '_default -> "-- Choose a company --",
'_showConstraints -> false
)
</fieldset>
<div class="actions">
<input type="submit" value="Create this computer" class="btn primary"> or
<a href="@routes.HomeController.list()" class="btn">Cancel</a>
</div>
}
}
@(id: Long, computerForm: Form[Computer], companies: Map[String, String])
@import helper._
@main {
<h1>Edit computer</h1>
@form(routes.HomeController.update(id)) {
<fieldset>
@CSRF.formField
@inputText(computerForm("name"), '_label -> "Computer name", '_help -> "")
@inputText(computerForm("introduced"), '_label -> "Introduced date", '_help -> "")
@inputText(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "")
@select(
computerForm("company.id"),
options(companies),
'_label -> "Company", '_default -> "-- Choose a company --",
'_showConstraints -> false
)
</fieldset>
<div class="actions">
<input type="submit" value="Save this computer" class="btn primary"> or
<a href="@routes.HomeController.list()" class="btn">Cancel</a>
</div>
}
@form(routes.HomeController.delete(id), 'class -> "topRight") {
@CSRF.formField
<input type="submit" value="Delete this computer" class="btn danger">
}
}
@(currentPage: io.ebean.PagedList[Computer], currentSortBy: String, currentOrder: String, currentFilter: String)
@****************************************
* Helper generating navigation links *
****************************************@
@link(newPage:Int, newSortBy:String) = @{
var sortBy = currentSortBy
var order = currentOrder
if(newSortBy != null) {
sortBy = newSortBy
if(currentSortBy == newSortBy) {
if(currentOrder == "asc") {
order = "desc"
} else {
order = "asc"
}
} else {
order = "asc"
}
}
// Generate the link
routes.HomeController.list(newPage, sortBy, order, currentFilter)
}
@**********************************
* Helper generating table headers *
***********************************@
@header(key:String, title:String) = {
<th class="@key.replace(".","_") header @if(currentSortBy == key) { @{if(currentOrder == "asc") "headerSortDown" else "headerSortUp" } }">
<a href="@link(0, key)">@title</a>
</th>
}
@main {
<h1 id="homeTitle">@Messages("computers.list.title", currentPage.getTotalCount)</h1>
@if(flash.containsKey("success")) {
<div class="alert-message warning">
<strong>Done!</strong> @flash.get("success")
</div>
}
<div id="actions">
<form action="@link(0, "name")" method="GET">
<input type="search" id="searchbox" name="f" value="@currentFilter" placeholder="Filter by computer name...">
<input type="submit" id="searchsubmit" value="Filter by name" class="btn primary">
</form>
<a class="btn success" id="add" href="@routes.HomeController.create()">Add a new computer</a>
</div>
@if(currentPage.getTotalCount == 0) {
<div class="well">
<em>Nothing to display</em>
</div>
} else {
<table class="computers zebra-striped">
<thead>
<tr>
@header("name", "Computer name")
@header("introduced", "Introduced")
@header("discontinued", "Discontinued")
@header("company.name", "Company")
</tr>
</thead>
<tbody>
@for(computer <- currentPage.getList.asScala) {
<tr>
<td><a href="@routes.HomeController.edit(computer.id)">@computer.name</a></td>
<td>
@if(computer.introduced == null) {
<em>-</em>
} else {
@computer.introduced.format("dd MMM yyyy")
}
</td>
<td>
@if(computer.discontinued == null) {
<em>-</em>
} else {
@computer.discontinued.format("dd MMM yyyy")
}
</td>
<td>
@if(computer.company == null) {
<em>-</em>
} else {
@computer.company.name
}
</td>
</tr>
}
</tbody>
</table>
<div id="pagination" class="pagination">
<ul>
@if(currentPage.hasPrev) {
<li class="prev">
<a href="@link(currentPage.getPageIndex - 1, null)">&larr; Previous</a>
</li>
} else {
<li class="prev disabled">
<a>&larr; Previous</a>
</li>
}
<li class="current">
<a>Displaying @currentPage.getDisplayXtoYofZ(" to "," of ")</a>
</li>
@if(currentPage.hasNext) {
<li class="next">
<a href="@link(currentPage.getPageIndex + 1, null)">Next &rarr;</a>
</li>
} else {
<li class="next disabled">
<a>Next &rarr;</a>
</li>
}
</ul>
</div>
}
}
@(content: Html)
<!DOCTYPE html>
<html>
<head>
<title>Computers database</title>
@*************************************
<link rel='stylesheet' href='@routes.Assets.at("lib/bootstrap/css/bootstrap.min.css")'>
<link rel='stylesheet' href='@routes.Assets.at("lib/font-awesome/css/font-awesome.min.css")'>
<script src="@routes.Assets.at("lib/jquery/jquery.js")" type="text/javascript"></script>
<script src="@routes.Assets.at("lib/bootstrap/js/bootstrap.min.js")" type="text/javascript"></script>
*************************************@
<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("stylesheets/bootstrap.min.css")">
<link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")"/>
</head>
<body>
<header class="topbar">
<h1 class="fill">
<a href="@routes.HomeController.index()">
Play sample application &mdash; Computer database
</a>
</h1>
</header>
<section id="main">
@content
</section>
</body>
</html>
name := "play-java-ebean-example"
version := "1.0.0-SNAPSHOT"
scalaVersion := "2.12.4"
crossScalaVersions := Seq("2.11.12", "2.12.4")
lazy val root = (project in file(".")).enablePlugins(PlayJava, PlayEbean)
libraryDependencies += guice
libraryDependencies += jdbc
libraryDependencies += "com.h2database" % "h2" % "1.4.196"
libraryDependencies += "org.awaitility" % "awaitility" % "2.0.0" % Test
libraryDependencies += "org.assertj" % "assertj-core" % "3.6.2" % Test
libraryDependencies += "org.mockito" % "mockito-core" % "2.1.0" % Test
testOptions in Test += Tests.Argument(TestFrameworks.JUnit, "-a", "-v")
# Configuration
# Database configuration
# ~~~~~
# You can declare as many datasources as you want.
# By convention, the default datasource is named `default`
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
# Ebean configuration
# ~~~~~
# You can declare as many Ebean servers as you want.
# By convention, the default server is named `default`
ebean.default="models.*"
# Assets configuration
# ~~~~~
"assets.cache./public/stylesheets/bootstrap.min.css"="max-age=3600"
# Number of database connections
# See https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
fixedConnectionPool = 9
# Set Hikari to fixed size
play.db {
prototype {
hikaricp.minimumIdle = ${fixedConnectionPool}
hikaricp.maximumPoolSize = ${fixedConnectionPool}
}
}
# Job queue sized to HikariCP connection pool
database.dispatcher {
executor = "thread-pool-executor"
throughput = 1
thread-pool-executor {
fixed-pool-size = ${fixedConnectionPool}
}
}
# --- First database schema
# --- !Ups
create table company (
id bigint not null,
name varchar(255),
constraint pk_company primary key (id))
;
create table computer (
id bigint not null,
name varchar(255),
introduced timestamp,
discontinued timestamp,
company_id bigint,
constraint pk_computer primary key (id))
;
create sequence company_seq start with 1000;
create sequence computer_seq start with 1000;
alter table computer add constraint fk_computer_company_1 foreign key (company_id) references company (id) on delete restrict on update restrict;
create index ix_computer_company_1 on computer (company_id);
# --- !Downs
SET REFERENTIAL_INTEGRITY FALSE;
drop table if exists company;
drop table if exists computer;
SET REFERENTIAL_INTEGRITY TRUE;
drop sequence if exists company_seq;
drop sequence if exists computer_seq;
This diff is collapsed.
<!--
~ Copyright (C) 2009-2016 Lightbend Inc. <https://www.lightbend.com>
-->
<!-- The default logback configuration that Play uses if no other configuration is provided -->
<configuration>
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${application.home:-.}/logs/application.log</file>
<encoder>
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
</encoder>
</appender>
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="STDOUT" />
</appender>
<logger name="play" level="INFO" />
<logger name="application" level="DEBUG" />
<logger name="com.gargoylesoftware.htmlunit" level="ERROR" />
<logger name="org.apache.http.client.protocol" level="ERROR" />
<root level="WARN">
<appender-ref ref="ASYNCFILE" />
<appender-ref ref="ASYNCSTDOUT" />
</root>
</configuration>
# Messages
computers.list.title={0,choice,0#No computers|1#One computer|1<{0,number,integer} computers} found
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Default path will just redirect to the computer list
GET / controllers.HomeController.index()
# Computers list (look at the default values for pagination parameters)
GET /computers controllers.HomeController.list(p:Int ?= 0, s ?= "name", o ?= "asc", f ?= "")
# Add computer
GET /computers/new controllers.HomeController.create()
POST /computers controllers.HomeController.save()
# Edit existing computer
GET /computers/:id controllers.HomeController.edit(id:Long)
POST /computers/:id controllers.HomeController.update(id:Long)
# Delete a computer
POST /computers/:id/delete controllers.HomeController.delete(id:Long)
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
// The Play plugin
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.13")
addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "4.1.0")
This diff is collapsed.
html {
background: #eee;
}
header h1 {
padding: 0.4em 1.1em;
color: white;
font-weight: normal;
font-size: 24px;
}
section#main {
position: relative;
padding: 5em 2em;
border-bottom: 1px solid #ccc;
min-height: 600px;
}
section#main .topRight {
position: absolute;
right: 20px;
top: 70px;
}
table.computers em {
color: #aaa;
}
table.computers .introduced, table.computers .discontinued {
width: 10%;
min-width: 100px;
}
table.computers .company_name {
width: 30%;
min-width: 300px;
}
table.computers .header a {
}
#actions {
position: relative;
}
#actions #add {
position: absolute;
right: 0;
top: 0;
}
#pagination {
position: relative;
}
#pagination ul {
position: absolute;
right: 0;
}
#pagination ul .current a {
color: #666;
}
import org.junit.Test;
import play.api.test.Helpers;
import play.test.WithBrowser;
import static org.fluentlenium.core.filter.FilterConstructor.withText;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
public class BrowserTest extends WithBrowser {
@Test
public void testBrowser() {
browser.goTo("http://localhost:" + Helpers.testServerPort());
assertThat(browser.$("header h1").first().text(), equalTo("Play sample application — Computer database"));
assertThat(browser.$("section h1").first().text(), equalTo("574 computers found"));
assertThat(browser.$("#pagination li.current").first().text(), equalTo("Displaying 1 to 10 of 574"));
browser.$("#pagination li.next a").click();
assertThat(browser.$("#pagination li.current").first().text(), equalTo("Displaying 11 to 20 of 574"));
browser.$("#searchbox").fill().with("Apple");
browser.$("#searchsubmit").click();
assertThat(browser.$("section h1").first().text(), equalTo("13 computers found"));
browser.$("a", withText("Apple II")).click();
assertThat(browser.$("section h1").first().text(), equalTo("Edit computer"));
browser.$("#discontinued").fill().with("10-10-2001");
browser.$("input.primary").click();
assertThat(browser.$("dl.error").size(), equalTo(1));
assertThat(browser.$("dl.error label").first().text() ,equalTo("Discontinued date"));
browser.$("#discontinued").fill().with("xxx");
browser.$("input.primary").click();
assertThat(browser.$("dl.error").size(), equalTo(1));
assertThat(browser.$("dl.error label").first().text(), equalTo("Discontinued date"));
browser.$("#discontinued").fill().with("");
browser.$("input.primary").click();
assertThat(browser.$("section h1").first().text(), equalTo("574 computers found"));
assertThat(browser.$(".alert-message").first().text(), equalTo("Done! Computer Apple II has been updated"));
browser.$("#searchbox").fill().with("Apple");
browser.$("#searchsubmit").click();
browser.$("a", withText("Apple II")).click();
browser.$("input.danger").click();
browser.takeHtmlDump("delete.html");
assertThat(browser.$("section h1").first().text(), equalTo("573 computers found"));
assertThat(browser.$(".alert-message").first().text(), equalTo("Done! Computer has been deleted"));
browser.$("#searchbox").fill().with("Apple");
browser.$("#searchsubmit").click();
assertThat(browser.$("section h1").first().text(), equalTo("12 computers found"));
}
}
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import play.mvc.Result;
import play.test.WithApplication;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static play.api.test.CSRFTokenHelper.addCSRFToken;
import static play.test.Helpers.*;
// Use FixMethodOrder to run the tests sequentially
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class FunctionalTest extends WithApplication {
@Test
public void redirectHomePage() {
Result result = route(app, controllers.routes.HomeController.index());
assertThat(result.status()).isEqualTo(SEE_OTHER);
assertThat(result.redirectLocation().get()).isEqualTo("/computers");
}
@Test
public void listComputersOnTheFirstPage() {
Result result = route(app, controllers.routes.HomeController.list(0, "name", "asc", ""));
assertThat(result.status()).isEqualTo(OK);
assertThat(contentAsString(result)).contains("574 computers found");
}
@Test
public void filterComputerByName() {
Result result = route(app, controllers.routes.HomeController.list(0, "name", "asc", "Apple"));
assertThat(result.status()).isEqualTo(OK);
assertThat(contentAsString(result)).contains("13 computers found");
}
@Test
public void createANewComputer() {
Result result = route(app, addCSRFToken(fakeRequest().uri(controllers.routes.HomeController.save().url())));
assertThat(result.status()).isEqualTo(OK);
Map<String, String> data = new HashMap<>();
data.put("name", "FooBar");
data.put("introduced", "badbadbad");
data.put("company.id", "1");
String saveUrl = controllers.routes.HomeController.save().url();
result = route(app, addCSRFToken(fakeRequest().bodyForm(data).method("POST").uri(saveUrl)));
assertThat(result.status()).isEqualTo(BAD_REQUEST);
assertThat(contentAsString(result)).contains("<option value=\"1\" selected=\"selected\">Apple Inc.</option>");
// <input type="text" id="introduced" name="introduced" value="badbadbad" aria-describedby="introduced_info_0 introduced_error_0" aria-invalid="true" class="form-control">
assertThat(contentAsString(result)).contains("<input type=\"text\" id=\"introduced\" name=\"introduced\" value=\"badbadbad\" ");
// <input type="text" id="name" name="name" value="FooBar" aria-describedby="name_info_0" required="true" class="form-control">
assertThat(contentAsString(result)).contains("<input type=\"text\" id=\"name\" name=\"name\" value=\"FooBar\" ");
data.put("introduced", "2011-12-24");
result = route(app, fakeRequest().bodyForm(data).method("POST").uri(saveUrl));
assertThat(result.status()).isEqualTo(SEE_OTHER);
assertThat(result.redirectLocation().get()).isEqualTo("/computers");
assertThat(result.flash().get("success")).isEqualTo("Computer FooBar has been created");
result = route(app, controllers.routes.HomeController.list(0, "name", "asc", "FooBar"));
assertThat(result.status()).isEqualTo(OK);
assertThat(contentAsString(result)).contains("One computer found");
}
}
import io.ebean.PagedList;
import models.Computer;
import org.junit.Test;
import play.Application;
import play.inject.guice.GuiceApplicationBuilder;
import play.test.WithApplication;
import repository.ComputerRepository;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
public class ModelTest extends WithApplication {
@Override
protected Application provideApplication() {
return new GuiceApplicationBuilder().build();
}
private String formatted(Date date) {
return new java.text.SimpleDateFormat("yyyy-MM-dd").format(date);
}
@Test
public void findById() {
final ComputerRepository computerRepository = app.injector().instanceOf(ComputerRepository.class);
final CompletionStage<Optional<Computer>> stage = computerRepository.lookup(21L);
await().atMost(1, SECONDS).until(() ->
assertThat(stage.toCompletableFuture()).isCompletedWithValueMatching(computerOptional -> {
final Computer macintosh = computerOptional.get();
return (macintosh.name.equals("Macintosh") && formatted(macintosh.introduced).equals("1984-01-24"));
})
);
}
@Test
public void pagination() {
final ComputerRepository computerRepository = app.injector().instanceOf(ComputerRepository.class);
CompletionStage<PagedList<Computer>> stage = computerRepository.page(1, 20, "name", "ASC", "");
// Test the completed result
await().atMost(1, SECONDS).until(() ->
assertThat(stage.toCompletableFuture()).isCompletedWithValueMatching(computers ->
computers.getTotalCount() == 574 &&
computers.getTotalPageCount() == 29 &&
computers.getList().size() == 20
)
);
}
}
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
ModelTest.class,
FunctionalTest.class,
BrowserTest.class
})
public class TestSuite {
// the class remains empty,
// used only as a holder for the above annotations
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment