package de.otto.edison.mongo.jobs;
import static java.time.Clock.systemDefaultZone;
import static java.time.OffsetDateTime.now;
import static java.time.temporal.ChronoUnit.SECONDS;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static de.otto.edison.jobs.domain.JobInfo.JobStatus.ERROR;
import static de.otto.edison.jobs.domain.JobInfo.JobStatus.OK;
import static de.otto.edison.jobs.domain.JobMessage.jobMessage;
import static de.otto.edison.mongo.jobs.DateTimeConverters.toDate;
import static de.otto.edison.mongo.jobs.JobStructure.ID;
import static de.otto.edison.mongo.jobs.JobStructure.JOB_TYPE;
import static de.otto.edison.mongo.jobs.JobStructure.MESSAGES;
import static de.otto.edison.mongo.jobs.JobStructure.MSG_LEVEL;
import static de.otto.edison.mongo.jobs.JobStructure.MSG_TEXT;
import static de.otto.edison.mongo.jobs.JobStructure.MSG_TS;
import static de.otto.edison.mongo.jobs.JobStructure.STATUS;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.assertj.core.util.Lists;
import org.bson.Document;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import com.github.fakemongo.Fongo;
import com.mongodb.client.MongoDatabase;
import de.otto.edison.jobs.domain.JobInfo;
import de.otto.edison.jobs.domain.JobInfo.JobStatus;
import de.otto.edison.jobs.domain.JobMessage;
import de.otto.edison.jobs.domain.Level;
public class MongoJobRepositoryTest {
private MongoJobRepository repo;
@Before
public void setup() {
final Fongo fongo = new Fongo("inmemory-mongodb");
final MongoDatabase mongoDatabase = fongo.getDatabase("jobsinfo");
repo = new MongoJobRepository(mongoDatabase, "jobsinfo");
}
@Test
public void shouldStoreAndRetrieveJobInfo() {
// given
final JobInfo foo = someJobInfo("http://localhost/foo/A");
final JobInfo writtenFoo = repo.create(foo);
// when
final Optional<JobInfo> jobInfo = repo.findOne("http://localhost/foo/A");
// then
assertThat(jobInfo.isPresent(), is(true));
assertThat(jobInfo.orElse(null), is(writtenFoo));
}
@Test
public void shouldUpdateJobInfo() {
// given
final JobInfo foo = someJobInfo("http://localhost/foo/B");
repo.createOrUpdate(foo);
final JobInfo writtenFoo = repo.createOrUpdate(foo.copy().addMessage(jobMessage(Level.INFO, "some message", now(foo.getClock()))).build());
// when
final Optional<JobInfo> jobInfo = repo.findOne("http://localhost/foo/B");
// then
assertThat(jobInfo.orElse(null), is(writtenFoo));
}
@Test
public void shouldUpdateJobStatus() {
//Given
final JobInfo foo = jobInfo("http://localhost/foo", "T_FOO"); //default jobStatus is 'OK'
repo.createOrUpdate(foo);
//When
repo.setJobStatus(foo.getJobId(), ERROR);
final JobStatus status = repo.findStatus("http://localhost/foo");
//Then
assertThat(status, is(ERROR));
}
@Test
public void shouldUpdateJobLastUpdateTime() {
//Given
final JobInfo foo = jobInfo("http://localhost/foo", "T_FOO");
repo.createOrUpdate(foo);
final OffsetDateTime myTestTime = OffsetDateTime.of(1979, 2, 5, 1, 2, 3, 4, ZoneOffset.UTC);
//When
repo.setLastUpdate(foo.getJobId(), myTestTime);
final Optional<JobInfo> jobInfo = repo.findOne(foo.getJobId());
//Then
assertThat(toDate(jobInfo.orElse(null).getLastUpdated()), is(toDate(myTestTime)));
}
@Test
public void shouldStoreAndRetrieveRunningJobInfo() {
// given
final JobInfo foo = someRunningJobInfo("http://localhost/foo", "SOME_JOB", now());
repo.createOrUpdate(foo);
// when
final Optional<JobInfo> jobInfo = repo.findOne("http://localhost/foo");
// then
assertThat(jobInfo.orElse(null), is(foo));
}
@Test
public void shouldStoreAndRetrieveAllJobInfo() {
// given
repo.createOrUpdate(someJobInfo("http://localhost/foo"));
repo.createOrUpdate(someJobInfo("http://localhost/bar"));
// when
final List<JobInfo> jobInfos = repo.findAll();
// then
assertThat(jobInfos, hasSize(2));
}
@Test
public void shouldStoreAndRetrieveAllJobInfoWithoutMessages() {
// given
final JobInfo job1 = someJobInfo("http://localhost/foo");
final JobInfo job2 = someJobInfo("http://localhost/bar");
repo.createOrUpdate(job1);
repo.createOrUpdate(job2);
// when
final List<JobInfo> jobInfos = repo.findAllJobInfoWithoutMessages();
// then
assertThat(jobInfos, hasSize(2));
assertThat(jobInfos.get(0), is(job1.copy().setMessages(emptyList()).build()));
assertThat(jobInfos.get(1), is(job2.copy().setMessages(emptyList()).build()));
}
@Test
public void shouldFindLatest() {
// given
repo.createOrUpdate(someRunningJobInfo("http://localhost/foo", "SOME_JOB", now()));
final JobInfo later = someRunningJobInfo("http://localhost/bar", "SOME_JOB", now().plus(1, SECONDS));
repo.createOrUpdate(later);
final JobInfo evenLater = someRunningJobInfo("http://localhost/foobar", "SOME_JOB", now().plus(2, SECONDS));
repo.createOrUpdate(evenLater);
// when
final List<JobInfo> jobInfos = repo.findLatest(2);
// then
assertThat(jobInfos, hasSize(2));
assertThat(jobInfos, containsInAnyOrder(later, evenLater));
}
@Test
public void shouldFindLatestByType() {
// given
final JobInfo oldest = someRunningJobInfo("http://localhost/foo", "SOME_JOB", now());
final JobInfo later = someRunningJobInfo("http://localhost/bar", "SOME_OTHER_JOB", now().plus(1, SECONDS));
final JobInfo evenLater = someRunningJobInfo("http://localhost/foobar", "SOME_JOB", now().plus(2, SECONDS));
repo.createOrUpdate(oldest);
repo.createOrUpdate(later);
repo.createOrUpdate(evenLater);
// when
final List<JobInfo> jobInfos = repo.findLatestBy("SOME_JOB", 2);
// then
assertThat(jobInfos, containsInAnyOrder(oldest, evenLater));
}
@Test
public void shouldFindByType() {
// given
final JobInfo foo = jobInfo("http://localhost/foo", "T_FOO");
repo.createOrUpdate(foo);
repo.createOrUpdate(jobInfo("http://localhost/bar", "T_BAR"));
// when
final List<JobInfo> jobInfos = repo.findByType("T_FOO");
// then
assertThat(jobInfos, contains(foo));
}
@Test
public void shouldFindRunningWithoutUpdateSince() {
// given
final JobInfo foo = jobInfo("http://localhost/foo", "T_FOO");
final JobInfo bar = someRunningJobInfo("http://localhost/bar", "T_BAR", now());
final JobInfo foobar = someRunningJobInfo("http://localhost/foobar", "T_BAR", now().plusSeconds(3));
repo.createOrUpdate(foo);
repo.createOrUpdate(bar);
repo.createOrUpdate(foobar);
// when
final List<JobInfo> infos = repo.findRunningWithoutUpdateSince(now().plusSeconds(2));
// then
assertThat(infos, hasSize(1));
assertThat(infos.get(0), is(bar));
}
@Test
public void shouldRemoveIfStopped() {
// given
final JobInfo foo = jobInfo("foo", "T_FOO");
final JobInfo bar = someRunningJobInfo("bar", "T_BAR", now());
repo.createOrUpdate(foo);
repo.createOrUpdate(bar);
// when
repo.removeIfStopped("foo");
repo.removeIfStopped("bar");
// then
assertThat(repo.findAll(), hasSize(1));
assertThat(repo.findAll(), contains(bar));
}
@Test
public void shouldFindAllJobTypes() throws Exception {
// Given
final OffsetDateTime now = OffsetDateTime.now();
final JobInfo eins = someRunningJobInfo("jobEins", "someJobTypeEins", now);
final JobInfo zwei = someRunningJobInfo("jobZwei", "someJobTypeZwei", now.plusSeconds(1));
final JobInfo drei = someRunningJobInfo("jobDrei", "someJobTypeDrei", now.plusSeconds(2));
final JobInfo vierWithTypeDrei = someRunningJobInfo("jobVier", "someJobTypeDrei", now.plusSeconds(3));
repo.createOrUpdate(eins);
repo.createOrUpdate(zwei);
repo.createOrUpdate(drei);
repo.createOrUpdate(vierWithTypeDrei);
// When
final List<String> allJobIds = repo.findAllJobIdsDistinct();
// Then
assertThat(allJobIds, hasSize(3));
assertThat(allJobIds, Matchers.containsInAnyOrder("jobEins", "jobZwei", "jobVier"));
}
@Test
public void shouldFindLatestDistinct() throws Exception {
// Given
final OffsetDateTime now = OffsetDateTime.now();
final JobInfo eins = someRunningJobInfo("http://localhost/eins", "someJobType", now);
final JobInfo zwei = someRunningJobInfo("http://localhost/zwei", "someOtherJobType", now.plusSeconds(1));
final JobInfo drei = someRunningJobInfo("http://localhost/drei", "nextJobType", now.plusSeconds(2));
final JobInfo vier = someRunningJobInfo("http://localhost/vier", "someJobType", now.plusSeconds(3));
final JobInfo fuenf = someRunningJobInfo("http://localhost/fuenf", "someJobType", now.plusSeconds(4));
repo.createOrUpdate(eins);
repo.createOrUpdate(zwei);
repo.createOrUpdate(drei);
repo.createOrUpdate(vier);
repo.createOrUpdate(fuenf);
// When
final List<JobInfo> latestDistinct = repo.findLatestJobsDistinct();
// Then
assertThat(latestDistinct, hasSize(3));
assertThat(latestDistinct, Matchers.containsInAnyOrder(fuenf, zwei, drei));
}
@Test
public void shouldNotFailInCaseLogMessageHasNoText() throws Exception {
//given
final Map<String, Object> infoLog = new HashMap<String, Object>() {{
put(MSG_LEVEL.key(), "INFO");
put(MSG_TEXT.key(), "Some text");
put(MSG_TS.key(), new Date());
}};
final Map<String, Object> errorLog = new HashMap<String, Object>() {{
put(MSG_LEVEL.key(), "ERROR");
put(MSG_TEXT.key(), null);
put(MSG_TS.key(), new Date());
}};
final Document infoLogDocument = new Document(infoLog);
final Document errorLogDocument = new Document(errorLog);
final Map<String, Object> jobLogs = new HashMap<String, Object>() {{
put(MESSAGES.key(), asList(infoLogDocument, errorLogDocument));
put(JOB_TYPE.key(), "SomeType");
put(ID.key(), "/SomeType/ID");
put(STATUS.key(), ERROR.toString());
}};
//when
final JobInfo jobInfo = repo.decode(new Document(jobLogs));
//then
assertThat(jobInfo.getMessages().size(), is(2));
}
@Test
public void shouldFindStatusOfAJob() throws Exception {
//Given
final JobInfo foo = jobInfo("http://localhost/foo", "T_FOO");
repo.createOrUpdate(foo);
//When
final JobStatus status = repo.findStatus("http://localhost/foo");
//Then
assertThat(status, is(OK));
}
@Test
public void shouldAppendMessageToJob() throws Exception {
// given
final String jobId = "http://localhost/baZ";
final JobInfo jobInfo = jobInfo(jobId, "T_FOO");
repo.createOrUpdate(jobInfo);
// when
final JobMessage jobMessage = JobMessage.jobMessage(Level.INFO, "Schön ist es auf der Welt zu sein, sagt der Igel zu dem Stachelschwein", now());
repo.appendMessage(jobId, jobMessage);
// then
final JobInfo jobInfoFromDB = repo.findOne(jobId).orElse(null);
assertThat(jobInfoFromDB.getMessages(), hasSize(3));
assertThat(jobInfoFromDB.getMessages().get(2), is(jobMessage));
assertThat(jobInfoFromDB.getStatus(), is(JobStatus.OK));
}
@Test
public void shouldDeleteJobInfos() throws Exception {
// given
repo.createOrUpdate(someJobInfo("http://localhost/foo"));
// when
repo.deleteAll();
// then
assertThat(repo.findAll(), is(Lists.emptyList()));
}
private JobInfo someJobInfo(final String jobId) {
return JobInfo.newJobInfo(
jobId,
"SOME_JOB",
now(), now(), Optional.of(now()), OK,
asList(
jobMessage(Level.INFO, "foo", now()),
jobMessage(Level.WARNING, "bar", now())),
systemDefaultZone(),
"localhost"
);
}
private JobInfo jobInfo(final String jobId, final String type) {
return JobInfo.newJobInfo(
jobId,
type,
now(), now(), Optional.of(now()), OK,
asList(
jobMessage(Level.INFO, "foo", now()),
jobMessage(Level.WARNING, "bar", now())),
systemDefaultZone(),
"localhost"
);
}
private JobInfo someRunningJobInfo(final String jobId, final String type, final OffsetDateTime started) {
return JobInfo.newJobInfo(
jobId,
type,
started, started.plus(1, SECONDS), Optional.empty(), OK,
Collections.emptyList(),
systemDefaultZone(),
"localhost"
);
}
}