/**
* Copyright (c) 2016, All Contributors (see CONTRIBUTORS file)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.eventsourcing.repository;
import com.eventsourcing.*;
import com.eventsourcing.events.EventCausalityEstablished;
import com.eventsourcing.hlc.HybridTimestamp;
import com.eventsourcing.hlc.NTPServerTimeProvider;
import com.eventsourcing.index.IndexEngine;
import com.eventsourcing.inmem.MemoryIndexEngine;
import com.googlecode.cqengine.index.support.CloseableIterator;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.SneakyThrows;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
public abstract class JournalTest<T extends Journal> {
protected T journal;
protected StandardRepository repository;
private IndexEngine indexEngine;
protected NTPServerTimeProvider timeProvider;
@SneakyThrows
public JournalTest(T journal) {
this.journal = journal;
}
@BeforeClass
public void setUpEnv() throws Exception {
repository = new StandardRepository();
repository.addCommandSetProvider(new PackageCommandSetProvider(new Package[]{JournalTest.class.getPackage()}));
repository.addEventSetProvider(new PackageEventSetProvider(new Package[]{JournalTest.class.getPackage()}));
repository.addEventSetProvider(new PackageEventSetProvider(new Package[]{EventCausalityEstablished.class
.getPackage()}));
repository.setJournal(this.journal);
timeProvider = new NTPServerTimeProvider(new String[]{"localhost"});
repository.setPhysicalTimeProvider(timeProvider);
repository.setLockProvider(new LocalLockProvider());
indexEngine = new MemoryIndexEngine();
repository.setIndexEngine(indexEngine);
repository.startAsync().awaitRunning();
}
@AfterClass
public void tearDownEnv() throws Exception {
repository.stopAsync().awaitTerminated();
}
@BeforeMethod
public void setUp() throws Exception {
journal.clear();
}
public static class TestEvent extends StandardEvent {
@Builder
public TestEvent(HybridTimestamp timestamp) {
super(timestamp);
}
}
public static class AnotherTestEvent extends StandardEvent {
@Builder
public AnotherTestEvent(HybridTimestamp timestamp) {
super(timestamp);
}
}
@EqualsAndHashCode(callSuper = false)
public static class TestCommand extends StandardCommand<Void, Void> {
@Getter
private final boolean events;
private TestEvent event;
@Builder
public TestCommand(HybridTimestamp timestamp, boolean events) {
super(timestamp);
this.events = events;
event = TestEvent.builder().build();
}
@Override
public EventStream<Void> events(Repository repository) throws Exception {
if (events) {
return EventStream.of(event);
} else {
return super.events(repository);
}
}
}
public static class ExceptionalTestCommand extends StandardCommand<Void, Void> {
@Builder
public ExceptionalTestCommand(HybridTimestamp timestamp) {
super(timestamp);
}
@Override
public EventStream<Void> events() throws Exception {
return EventStream.of(Stream.generate(() -> {
throw new IllegalStateException();
}));
}
}
@Test
@SneakyThrows
public void journalRetrieving() {
HybridTimestamp timestamp = new HybridTimestamp(timeProvider);
timestamp.update();
TestCommand command = TestCommand.builder().events(true).build();
command.timestamp(timestamp);
Journal.Transaction tx = journal.beginTransaction();
journal.journal(tx, command);
journal.journal(tx, command.event);
assertFalse(journal.get(command.uuid()).isPresent());
assertFalse(journal.get(command.event.uuid()).isPresent());
tx.commit();
Optional<Entity> entity = journal.get(command.uuid());
assertTrue(entity.isPresent());
assertEquals(command.uuid(), entity.get().uuid());
Optional<Entity> eventEntity = journal.get(command.event.uuid());
assertTrue(eventEntity.isPresent());
assertEquals(command.event.uuid(), eventEntity.get().uuid());
}
@Test
@SneakyThrows
public void journalIterating() {
HybridTimestamp timestamp = new HybridTimestamp(timeProvider);
timestamp.update();
TestCommand command1 = TestCommand.builder().events(true).build();
TestCommand command2 = TestCommand.builder().events(true).build();
Journal.Transaction tx = journal.beginTransaction();
journal.journal(tx, command1);
journal.journal(tx, command1.event);
tx.commit();
tx = journal.beginTransaction();
journal.journal(tx, command2);
journal.journal(tx, command2.event);
tx.commit();
CloseableIterator<EntityHandle<TestCommand>> commandIterator = journal.commandIterator(TestCommand.class);
List<EntityHandle<TestCommand>> commands = StreamSupport
.stream(Spliterators.spliteratorUnknownSize(commandIterator, Spliterator.IMMUTABLE), false)
.collect(Collectors.toList());
commandIterator.close();
assertEquals(commands.size(), 2);
assertTrue(commands.stream().anyMatch(c -> c.uuid().equals(command1.uuid())));
assertTrue(commands.stream().anyMatch(c -> c.uuid().equals(command2.uuid())));
CloseableIterator<EntityHandle<TestEvent>> eventIterator = journal.eventIterator(TestEvent.class);
List<UUID> iteratedEvents = StreamSupport
.stream(Spliterators.spliteratorUnknownSize(eventIterator, Spliterator.IMMUTABLE), false)
.map(EntityHandle::uuid)
.collect(Collectors.toList());
eventIterator.close();
assertTrue(iteratedEvents.containsAll(Arrays.asList(command1.event.uuid(), command2.event.uuid())));
}
}