Skip to content
Permalink
Browse files
Merge pull request #5 from myrle-krantz/develop
Progress on making sure events are sent the right number of times.
  • Loading branch information
myrle-krantz committed May 19, 2017
2 parents f9ef719 + b6117f6 commit 329b53bb9b1229e9e01a1f524a750f5765fb2a65
Showing 45 changed files with 876 additions and 628 deletions.
@@ -2,7 +2,7 @@

[![Join the chat at https://gitter.im/mifos-initiative/mifos.io](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mifos-initiative/mifos.io?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

This project provides a heart beat for other microservices which need to start jobs to be executed once and only once at a defined time.
This project provides a heart beatPublish for other microservices which need to start jobs to be executed once and only once at a defined time.

## Abstract
Mifos I/O is an application framework for digital financial services, a system to support nationwide and cross-national financial transactions and help to level and speed the creation of an inclusive, interconnected digital economy for every nation in the world.
@@ -16,7 +16,6 @@
package io.mifos.rhythm.api.v1.client;

import io.mifos.core.api.util.CustomFeignClientsConfiguration;
import io.mifos.rhythm.api.v1.domain.Application;
import io.mifos.rhythm.api.v1.domain.Beat;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.http.MediaType;
@@ -33,29 +32,6 @@
@FeignClient(value="rhythm-v1", path="/rhythm/v1", configuration = CustomFeignClientsConfiguration.class)
public interface RhythmManager {

@RequestMapping(
value = "/applications",
method = RequestMethod.GET,
produces = MediaType.ALL_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE
)
List<Application> getAllApplications();

@RequestMapping(
value = "/applications/{applicationname}",
method = RequestMethod.GET,
produces = MediaType.ALL_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
Application getApplication(@PathVariable("applicationname") final String applicationName);

@RequestMapping(
value = "/applications",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE
)
void createApplication(final Application application);

@RequestMapping(
value = "/applications/{applicationname}",
method = RequestMethod.DELETE,

This file was deleted.

@@ -32,6 +32,11 @@ public class Beat {
public Beat() {
}

public Beat(String identifier, Integer alignmentHour) {
this.identifier = identifier;
this.alignmentHour = alignmentHour;
}

public Beat(String identifier) {
this.identifier = identifier;
}
@@ -1,3 +1,18 @@
/*
* Copyright 2017 The Mifos Initiative.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.mifos.rhythm.api.v1.events;

import java.util.Objects;
@@ -24,12 +24,10 @@ public interface EventConstants {
String DESTINATION = "rhythm-v1";
String SELECTOR_NAME = "action";
String INITIALIZE = "initialize";
String POST_APPLICATION = "post-application";
String POST_BEAT = "post-beat";
String DELETE_APPLICATION = "delete-application";
String DELETE_BEAT = "delete-beat";
String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
String SELECTOR_POST_APPLICATION = SELECTOR_NAME + " = '" + POST_APPLICATION + "'";
String SELECTOR_POST_BEAT = SELECTOR_NAME + " = '" + POST_BEAT + "'";
String SELECTOR_DELETE_APPLICATION = SELECTOR_NAME + " = '" + DELETE_APPLICATION + "'";
String SELECTOR_DELETE_BEAT = SELECTOR_NAME + " = '" + DELETE_BEAT + "'";

This file was deleted.

@@ -33,7 +33,7 @@ public BeatTest(ValidationTestCase<Beat> testCase) {

@Override
protected Beat createValidTestSubject() {
return new Beat();
return new Beat("boop", 0);
}

@Parameterized.Parameters
@@ -4,20 +4,27 @@ task publishApiToMavenLocal {
dependsOn gradle.includedBuild('api').task(':publishToMavenLocal')
}

task publishSpiToMavenLocal {
dependsOn gradle.includedBuild('spi').task(':publishToMavenLocal')
}

task publishServiceToMavenLocal {
mustRunAfter publishApiToMavenLocal
mustRunAfter publishSpiToMavenLocal
dependsOn gradle.includedBuild('service').task(':publishToMavenLocal')
}

task publishComponentTestToMavenLocal {
mustRunAfter publishApiToMavenLocal
mustRunAfter publishSpiToMavenLocal
mustRunAfter publishServiceToMavenLocal
dependsOn gradle.includedBuild('component-test').task(':publishToMavenLocal')
}

task publishToMavenLocal {
group 'all'
dependsOn publishApiToMavenLocal
dependsOn publishSpiToMavenLocal
dependsOn publishServiceToMavenLocal
dependsOn publishComponentTestToMavenLocal
}
@@ -31,6 +38,7 @@ task prepareForTest {
task licenseFormat {
group 'all'
dependsOn gradle.includedBuild('api').task(':licenseFormat')
dependsOn gradle.includedBuild('spi').task(':licenseFormat')
dependsOn gradle.includedBuild('service').task(':licenseFormat')
dependsOn gradle.includedBuild('component-test').task(':licenseFormat')
}
@@ -24,19 +24,23 @@
import io.mifos.core.test.listener.EnableEventRecording;
import io.mifos.core.test.listener.EventRecorder;
import io.mifos.rhythm.api.v1.client.RhythmManager;
import io.mifos.rhythm.api.v1.domain.Application;
import io.mifos.rhythm.api.v1.domain.Beat;
import io.mifos.rhythm.api.v1.events.BeatEvent;
import io.mifos.rhythm.api.v1.events.EventConstants;
import io.mifos.rhythm.service.RhythmConfiguration;
import io.mifos.rhythm.service.internal.service.BeatPublisherService;
import org.junit.*;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
@@ -45,12 +49,20 @@
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;

import static org.mockito.Matchers.*;

/**
* @author Myrle Krantz
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
classes = {AbstractRhythmTest.TestConfiguration.class})
classes = {AbstractRhythmTest.TestConfiguration.class},
properties = {"rhythm.user=homer", "rhythm.beatCheckRate=500"}
)
public class AbstractRhythmTest {

private static final String APP_NAME = "rhythm-v1";
@@ -76,7 +88,7 @@ public Logger logger() {
private final static TestEnvironment testEnvironment = new TestEnvironment(APP_NAME);
private final static CassandraInitializer cassandraInitializer = new CassandraInitializer();
private final static MariaDBInitializer mariaDBInitializer = new MariaDBInitializer();
private final static TenantDataStoreContextTestRule tenantDataStoreContext = TenantDataStoreContextTestRule.forRandomTenantName(cassandraInitializer, mariaDBInitializer);
final static TenantDataStoreContextTestRule tenantDataStoreContext = TenantDataStoreContextTestRule.forRandomTenantName(cassandraInitializer, mariaDBInitializer);

@ClassRule
public static TestRule orderClassRules = RuleChain
@@ -97,6 +109,9 @@ public Logger logger() {
@Autowired
EventRecorder eventRecorder;

@SpyBean
BeatPublisherService beatPublisherServiceSpy;

@Before
public void prepTest() {
userContext = tenantApplicationSecurityEnvironment.createAutoUserContext(TEST_USER);
@@ -116,21 +131,32 @@ public boolean waitForInitialize() {
}
}

Application createApplication(final String name) throws InterruptedException {
final Application application = new Application(name);
this.testSubject.createApplication(application);
Beat createBeat(final String applicationName, final String beatIdentifier) throws InterruptedException {
final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));

Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_APPLICATION, application.getApplicationName()));
return application;
}

Beat createBeat(final Application application, final String beatIdentifier) throws InterruptedException {
final Beat beat = new Beat();
beat.setIdentifier(beatIdentifier);
beat.setAlignmentHour(0);
this.testSubject.createBeat(application.getApplicationName(), beat);
beat.setAlignmentHour(now.getHour());

final LocalDateTime expectedBeatTimestamp = getExpectedBeatTimestamp(now, beat.getAlignmentHour());
Mockito.doReturn(true).when(beatPublisherServiceSpy).publishBeat(Matchers.eq(beatIdentifier), Matchers.eq(tenantDataStoreContext.getTenantName()), Matchers.eq(applicationName),
AdditionalMatchers.or(Matchers.eq(expectedBeatTimestamp), Matchers.eq(getNextTimeStamp(expectedBeatTimestamp))));

this.testSubject.createBeat(applicationName, beat);

Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BEAT, new BeatEvent(applicationName, beat.getIdentifier())));

Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(2_000).times(1)).publishBeat(beatIdentifier, tenantDataStoreContext.getTenantName(), applicationName, expectedBeatTimestamp);

Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BEAT, new BeatEvent(application.getApplicationName(), beat.getIdentifier())));
return beat;
}

LocalDateTime getExpectedBeatTimestamp(final LocalDateTime fromTime, final Integer alignmentHour) {
final LocalDateTime midnight = fromTime.truncatedTo(ChronoUnit.DAYS);
return midnight.plusHours(alignmentHour);
}

private LocalDateTime getNextTimeStamp(final LocalDateTime fromTime) {
return fromTime.plusDays(1);
}
}

0 comments on commit 329b53b

Please sign in to comment.