Skip to content

Commit cb4c8f8

Browse files
committed
Makes repo initialization failures testable
1 parent af90ac2 commit cb4c8f8

File tree

6 files changed

+136
-48
lines changed

6 files changed

+136
-48
lines changed

src/main/java/nl/jqno/paralleljava/Main.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import nl.jqno.paralleljava.app.logging.Slf4jLogger;
1010
import nl.jqno.paralleljava.app.persistence.RandomIdGenerator;
1111
import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository;
12+
import nl.jqno.paralleljava.app.persistence.database.DefaultJdbi;
1213
import nl.jqno.paralleljava.app.persistence.database.TodoMapper;
1314
import nl.jqno.paralleljava.app.serialization.GsonSerializer;
1415
import nl.jqno.paralleljava.app.server.SparkServer;
@@ -26,7 +27,9 @@ public static void main(String... args) {
2627
var jdbcUrl = environment.jdbcUrl().getOrElse(Environment.DEFAULT_JDBC_URL);
2728

2829
var todoMapper = new TodoMapper(fullUrl);
29-
var repository = new DatabaseRepository(jdbcUrl, todoMapper, loggerFactory);
30+
var jdbi = new DefaultJdbi(jdbcUrl, todoMapper, loggerFactory);
31+
var repository = new DatabaseRepository(jdbi);
32+
3033
var idGenerator = new RandomIdGenerator();
3134
var serializer = GsonSerializer.create(loggerFactory);
3235
var controller = new DefaultController(fullUrl, repository, idGenerator, serializer, loggerFactory);

src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,7 @@
44
import io.vavr.control.Option;
55
import io.vavr.control.Try;
66
import nl.jqno.paralleljava.app.domain.Todo;
7-
import nl.jqno.paralleljava.app.logging.Logger;
8-
import nl.jqno.paralleljava.app.logging.LoggerFactory;
97
import nl.jqno.paralleljava.app.persistence.Repository;
10-
import org.jdbi.v3.core.HandleCallback;
11-
import org.jdbi.v3.core.HandleConsumer;
12-
import org.jdbi.v3.core.Jdbi;
138

149
import java.util.UUID;
1510

@@ -18,24 +13,27 @@
1813
*/
1914
public class DatabaseRepository implements Repository {
2015

21-
private final Logger logger;
22-
private final Jdbi jdbi;
16+
private Jdbi jdbi;
2317

24-
public DatabaseRepository(String jdbcUrl, TodoMapper todoMapper, LoggerFactory loggerFactory) {
25-
this.jdbi = Jdbi.create(jdbcUrl);
26-
this.jdbi.registerRowMapper(Todo.class, todoMapper);
27-
this.logger = loggerFactory.create(getClass());
28-
logger.forProduction("Using database " + jdbcUrl);
18+
public DatabaseRepository(Jdbi jdbi) {
19+
this.jdbi = jdbi;
2920
}
3021

3122
public Try<Void> initialize() {
3223
var sql = "CREATE TABLE todo (id VARCHAR(36) PRIMARY KEY, title VARCHAR, completed BOOLEAN, index INTEGER)";
33-
return execute(handle -> handle.execute(sql))
34-
.orElse(Try.success(null)); // If it fails, the table probably already exists.
24+
return jdbi.execute(handle -> handle.execute(sql))
25+
.recoverWith(f -> {
26+
if (f.getMessage() != null && f.getMessage().toLowerCase().contains("\"todo\" already exists")) {
27+
return Try.success(null);
28+
}
29+
else {
30+
return Try.failure(f);
31+
}
32+
});
3533
}
3634

3735
public Try<Void> create(Todo todo) {
38-
return execute(handle ->
36+
return jdbi.execute(handle ->
3937
handle.createUpdate("INSERT INTO todo (id, title, completed, index) VALUES (:id, :title, :completed, :order)")
4038
.bind("id", todo.id().toString())
4139
.bind("title", todo.title())
@@ -45,7 +43,7 @@ public Try<Void> create(Todo todo) {
4543
}
4644

4745
public Try<Option<Todo>> get(UUID id) {
48-
return query(handle -> {
46+
return jdbi.query(handle -> {
4947
var o = handle.createQuery("SELECT id, title, completed, index FROM todo WHERE id = :id")
5048
.bind("id", id.toString())
5149
.mapTo(Todo.class)
@@ -55,14 +53,14 @@ public Try<Option<Todo>> get(UUID id) {
5553
}
5654

5755
public Try<List<Todo>> getAll() {
58-
return query(handle ->
56+
return jdbi.query(handle ->
5957
handle.createQuery("SELECT id, title, completed, index FROM todo")
6058
.mapTo(Todo.class)
6159
.collect(List.collector()));
6260
}
6361

6462
public Try<Void> update(Todo todo) {
65-
return execute(handle ->
63+
return jdbi.execute(handle ->
6664
handle.createUpdate("UPDATE todo SET title = :title, completed = :completed, index = :order WHERE id = :id")
6765
.bind("title", todo.title())
6866
.bind("completed", todo.completed())
@@ -72,25 +70,13 @@ public Try<Void> update(Todo todo) {
7270
}
7371

7472
public Try<Void> delete(UUID id) {
75-
return execute(handle ->
73+
return jdbi.execute(handle ->
7674
handle.createUpdate("DELETE FROM todo WHERE id = :id")
7775
.bind("id", id.toString())
7876
.execute());
7977
}
8078

8179
public Try<Void> deleteAll() {
82-
return execute(handle -> handle.execute("DELETE FROM todo"));
83-
}
84-
85-
private <X extends Exception> Try<Void> execute(HandleConsumer<X> consumer) {
86-
return Try.<Void>of(() -> {
87-
jdbi.useHandle(consumer);
88-
return null;
89-
}).onFailure(f -> logger.wakeMeUp("Failed to execute statement", f));
90-
}
91-
92-
private <T, X extends Exception> Try<T> query(HandleCallback<T, X> callback) {
93-
return Try.of(() -> jdbi.withHandle(callback))
94-
.onFailure(f -> logger.wakeMeUp("Failed to execute query", f));
80+
return jdbi.execute(handle -> handle.execute("DELETE FROM todo"));
9581
}
9682
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package nl.jqno.paralleljava.app.persistence.database;
2+
3+
import io.vavr.control.Try;
4+
import nl.jqno.paralleljava.app.logging.Logger;
5+
import nl.jqno.paralleljava.app.logging.LoggerFactory;
6+
import org.jdbi.v3.core.HandleCallback;
7+
import org.jdbi.v3.core.HandleConsumer;
8+
9+
public class DefaultJdbi implements Jdbi {
10+
private final org.jdbi.v3.core.Jdbi jdbi;
11+
private final Logger logger;
12+
13+
public DefaultJdbi(String jdbcUrl, TodoMapper todoMapper, LoggerFactory loggerFactory) {
14+
this.jdbi = org.jdbi.v3.core.Jdbi
15+
.create(jdbcUrl)
16+
.registerRowMapper(todoMapper);
17+
this.logger = loggerFactory.create(getClass());
18+
}
19+
20+
public <X extends Exception> Try<Void> execute(HandleConsumer<X> consumer) {
21+
return Try.<Void>of(() -> {
22+
jdbi.useHandle(consumer);
23+
return null;
24+
}).onFailure(f -> logger.wakeMeUp("Failed to execute statement", f));
25+
}
26+
27+
public <T, X extends Exception> Try<T> query(HandleCallback<T, X> callback) {
28+
return Try.of(() -> jdbi.withHandle(callback))
29+
.onFailure(f -> logger.wakeMeUp("Failed to execute query", f));
30+
}
31+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package nl.jqno.paralleljava.app.persistence.database;
2+
3+
import io.vavr.control.Try;
4+
import org.jdbi.v3.core.HandleCallback;
5+
import org.jdbi.v3.core.HandleConsumer;
6+
7+
public interface Jdbi {
8+
<X extends Exception> Try<Void> execute(HandleConsumer<X> consumer);
9+
<T, X extends Exception> Try<T> query(HandleCallback<T, X> callback);
10+
}

src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package nl.jqno.paralleljava.app.persistence.database;
22

3-
import nl.jqno.paralleljava.app.domain.Todo;
4-
import nl.jqno.paralleljava.app.environment.Environment;
5-
import nl.jqno.paralleljava.app.logging.LoggerFactory;
63
import nl.jqno.paralleljava.TestData;
74
import nl.jqno.paralleljava.TestData.AnotherTodo;
85
import nl.jqno.paralleljava.TestData.SomeTodo;
6+
import nl.jqno.paralleljava.app.domain.Todo;
7+
import nl.jqno.paralleljava.app.environment.Environment;
8+
import nl.jqno.paralleljava.app.logging.LoggerFactory;
99
import nl.jqno.paralleljava.app.logging.NopLogger;
1010
import nl.jqno.picotest.Test;
1111
import org.jdbi.v3.core.statement.StatementContext;
@@ -20,25 +20,46 @@ public class DatabaseRepositoryTest extends Test {
2020

2121
private static final String IN_MEMORY_DATABASE = Environment.DEFAULT_JDBC_URL;
2222
private static final LoggerFactory NOP_LOGGER = c -> new NopLogger();
23-
private final TodoMapper todoMapper = new TodoMapper(TestData.URL_PREFIX);
23+
private static final TodoMapper todoMapper = new TodoMapper(TestData.URL_PREFIX);
2424

2525
public void initialization() {
2626

2727
test("a table is created", () -> {
28-
var repo = new DatabaseRepository(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER);
28+
var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER);
29+
var repo = new DatabaseRepository(jdbi);
2930
var result = repo.initialize();
3031
assertThat(result).isSuccess();
3132
});
3233

3334
test("initializing twice is a no-op the second time", () -> {
34-
var repo = new DatabaseRepository(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER);
35+
var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER);
36+
var repo = new DatabaseRepository(jdbi);
3537
assertThat(repo.initialize()).isSuccess();
3638
assertThat(repo.initialize()).isSuccess();
3739
});
40+
41+
test("a failure with no message while creating is propagated", () -> {
42+
var jdbi = new FailingJdbi();
43+
var repo = new DatabaseRepository(jdbi);
44+
assertThat(repo.initialize()).isFailure();
45+
});
46+
47+
test("a failure with a message while creating is propagated", () -> {
48+
var jdbi = new FailingJdbi(new IllegalStateException("Something went wrong"));
49+
var repo = new DatabaseRepository(jdbi);
50+
assertThat(repo.initialize()).isFailure();
51+
});
52+
53+
test("a failure to create the table is not propagated if the table already exists", () -> {
54+
var jdbi = new FailingJdbi(new IllegalStateException("Table \"TODO\" already exists"));
55+
var repo = new DatabaseRepository(jdbi);
56+
assertThat(repo.initialize()).isSuccess();
57+
});
3858
}
3959

4060
public void repository() {
41-
var repo = new DatabaseRepository(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER);
61+
var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER);
62+
var repo = new DatabaseRepository(jdbi);
4263

4364
beforeAll(() -> {
4465
assertThat(repo.initialize()).isSuccess();
@@ -93,18 +114,30 @@ public void repository() {
93114
}
94115

95116
public void failures() {
96-
var failingMapper = new TodoMapper("") {
97-
public Todo map(ResultSet rs, StatementContext ctx) throws SQLException {
98-
throw new SQLException("Intentional failure");
99-
}
100-
};
101-
var repo = new DatabaseRepository(IN_MEMORY_DATABASE, failingMapper, NOP_LOGGER);
117+
test("Execute failures cause failed results", () -> {
118+
var jdbi = new FailingJdbi();
119+
var repo = new DatabaseRepository(jdbi);
102120

103-
beforeAll(() -> {
104-
repo.initialize();
121+
var result = repo.create(SomeTodo.TODO);
122+
assertThat(result).isFailure();
105123
});
106124

107125
test("Query failures cause failed results", () -> {
126+
var jdbi = new FailingJdbi();
127+
var repo = new DatabaseRepository(jdbi);
128+
129+
var result = repo.getAll();
130+
assertThat(result).isFailure();
131+
});
132+
133+
test("Mapping failures cause failed results", () -> {
134+
var failingMapper = new TodoMapper("") {
135+
public Todo map(ResultSet rs, StatementContext ctx) throws SQLException {
136+
throw new SQLException("Intentional failure");
137+
}
138+
};
139+
var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, failingMapper, NOP_LOGGER);
140+
var repo = new DatabaseRepository(jdbi);
108141
repo.create(SomeTodo.TODO);
109142

110143
var result = repo.getAll();
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package nl.jqno.paralleljava.app.persistence.database;
2+
3+
import io.vavr.control.Try;
4+
import org.jdbi.v3.core.HandleCallback;
5+
import org.jdbi.v3.core.HandleConsumer;
6+
7+
public class FailingJdbi implements Jdbi {
8+
private final Throwable exception;
9+
10+
public FailingJdbi() {
11+
this(new IllegalStateException());
12+
}
13+
14+
public FailingJdbi(Throwable exception) {
15+
this.exception = exception;
16+
}
17+
18+
public <X extends Exception> Try<Void> execute(HandleConsumer<X> consumer) {
19+
return Try.failure(exception);
20+
}
21+
22+
public <T, X extends Exception> Try<T> query(HandleCallback<T, X> callback) {
23+
return Try.failure(exception);
24+
}
25+
}

0 commit comments

Comments
 (0)