Skip to content

Commit

Permalink
Merge pull request #67 from NashTech-Labs/feature/Added-shipment_test…
Browse files Browse the repository at this point in the history
…_cases

Added test cases and ReadMe file for shipment service
  • Loading branch information
dev-swapnildixit4161 authored Jan 15, 2024
2 parents 6945d33 + 34ef9c7 commit 15a65bc
Show file tree
Hide file tree
Showing 9 changed files with 502 additions and 5 deletions.
43 changes: 43 additions & 0 deletions shipment-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## Shipment Service

#### Application Overview
This application is designed to manage the shipment process in a system using the Axon Framework for CQRS (Command Query Responsibility Segregation) and Event Sourcing. It includes components for handling the creation of shipments, persisting shipment details in a database, and publishing events to a Google Cloud Pub/Sub topic.

## Workflow

### Shipment Creation:
* The process begins with a CreateShipmentCommand, which is handled by the ShipmentAggregate class.
* The aggregate processes the command, applies the ShipmentCreatedEvent, and triggers the OrderShippedEvent.

### Event Handling:
* The ShipmentCreatedEvent is handled by the ShipmentEventHandler.
* The event handler extracts user information and shipment details from the event.
* It saves the shipment details to a database using the ShipmentRepository.
* Additionally, it uses the PubSubPublisherService to publish the shipment details to a Google Cloud Pub/Sub topic.

### Google Cloud Pub/Sub Integration:
* The PubSubPublisherService initializes the Pub/Sub publisher during application startup (@PostConstruct).
* The publisher is responsible for converting shipment data into a Pub/Sub message and publishing it to the configured topic.
* During application shutdown (@PreDestroy), the publisher is gracefully shut down.

### Data Storage:
* Shipment details, including user information, are persisted to a database for future reference.
* The ShipmentRepository handles the database interactions.

## Key Components

### ShipmentAggregate:

Manages the state and behavior related to the creation of shipments.
Applies events to update its internal state.

### ShipmentEventHandler:
Listens for the ShipmentCreatedEvent.
Extracts shipment and user details from the event.
Saves shipment details to a database using the ShipmentRepository.
Publishes shipment details to Google Cloud Pub/Sub using the PubSubPublisherService.

### PubSubPublisherService:
Initializes a Google Cloud Pub/Sub publisher during application startup.
Publishes shipment data to a Pub/Sub topic.
Gracefully shuts down the publisher during application shutdown.
7 changes: 7 additions & 0 deletions shipment-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.axonframework/axon-test -->
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-test</artifactId>
<version>4.8.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.google.cloud</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.nashtech.common.event.OrderShippedEvent;
import com.nashtech.common.event.ShipmentCreatedEvent;
import com.nashtech.common.model.User;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.eventsourcing.EventSourcingHandler;
Expand All @@ -13,6 +14,7 @@

@Aggregate
@Slf4j
@Getter
public class ShipmentAggregate {

@AggregateIdentifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import com.google.pubsub.v1.TopicName;
import com.nashtech.shipment.config.GCPConfig;
import com.nashtech.shipment.entity.ShipmentEntity;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
Expand All @@ -26,15 +24,23 @@ public class PubSubPublisherService {
private GCPConfig gcpConfig;
private ObjectMapper objectMapper;

@PostConstruct
public PubSubPublisherService(GCPConfig gcpConfig, Publisher publisher) {
this.gcpConfig = gcpConfig;
TopicName topicName = TopicName.of(gcpConfig.getProjectId(), gcpConfig.getTopicId());
objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
this.publisher = publisher;
}

public void init() throws IOException {
TopicName topicName = TopicName.of(gcpConfig.getProjectId(), gcpConfig.getTopicId());
publisher = Publisher.newBuilder(topicName).build();
objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
if (publisher == null) {
publisher = Publisher.newBuilder(topicName).build();
}
}

@PreDestroy
public void cleanup() {
try {
if (publisher != null) {
Expand All @@ -52,12 +58,15 @@ public void messagePublisher(final ShipmentEntity shipmentEntity) {
log.info("Publishing data to topic: {}", gcpConfig.getTopicId());

try {
init();
String shipmentData = objectMapper.writeValueAsString(shipmentEntity);
PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(shipmentData)).build();
ApiFuture<String> publishedMessage = publisher.publish(pubsubMessage);
log.info("Message id generated:{}", publishedMessage.get());
} catch (Exception exception) {
log.error("Error : {} while publishing data to pub sub topic : {}", exception.getMessage(), gcpConfig.getTopicId());
} finally {
cleanup();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package com.nashtech.shipment.aggregate;

import com.nashtech.common.command.CreateShipmentCommand;
import com.nashtech.common.event.OrderShippedEvent;
import com.nashtech.common.event.ShipmentCreatedEvent;
import com.nashtech.shipment.util.ObjectCreator;
import com.nashtech.shipment.util.TestTag;
import org.axonframework.test.aggregate.AggregateTestFixture;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

@Tag(TestTag.SMALL_TESTS)
class ShipmentAggregateTest {
private AggregateTestFixture<ShipmentAggregate> fixture;

@BeforeEach
void setUp() {
fixture = new AggregateTestFixture<>(ShipmentAggregate.class);
}

@Test
void testShipmentAggregateCreation() {

CreateShipmentCommand createShipmentCommand = ObjectCreator.getShipmentCommand();

ShipmentCreatedEvent shipmentCreatedEvent = ObjectCreator.getShipmentCreatedEvent();

OrderShippedEvent shippedEvent = ObjectCreator.getOrderShippedEvent();

fixture.givenNoPriorActivity()
.when(createShipmentCommand)
.expectEvents(shipmentCreatedEvent, shippedEvent);
}


@Test
void test_shipment_created_event_handling_with_null_values() {
ShipmentAggregate shipmentAggregate = new ShipmentAggregate();
ShipmentCreatedEvent shipmentCreatedEvent = ShipmentCreatedEvent.builder().build();

shipmentAggregate.on(shipmentCreatedEvent);

assertNull(shipmentAggregate.getShipmentId());
assertNull(shipmentAggregate.getOrderId());
assertNull(shipmentAggregate.getPaymentId());
assertNull(shipmentAggregate.getProductId());
assertNull(shipmentAggregate.getBrand());
assertNull(shipmentAggregate.getQuantity());
assertNull(shipmentAggregate.getBasePrice());
assertNull(shipmentAggregate.getSubTotal());
assertNull(shipmentAggregate.getTotal());
assertNull(shipmentAggregate.getTax());
assertNull(shipmentAggregate.getTotalTax());
assertNull(shipmentAggregate.getUser());
}

@Test
void test_shipment_aggregate_state_update() {

ShipmentCreatedEvent shipmentCreatedEvent = ShipmentCreatedEvent.builder()
.shipmentId("shipmentId")
.orderId("orderId")
.paymentId("paymentId")
.productId("productId")
.quantity(10)
.brand("brand")
.basePrice(100.0)
.tax(0.1f)
.totalTax(10.0f)
.subTotal(1000.0)
.total(1100.0)
.user(ObjectCreator.getUser())
.build();
ShipmentAggregate shipmentAggregate = new ShipmentAggregate();
shipmentAggregate.on(shipmentCreatedEvent);
assertEquals(shipmentCreatedEvent.getShipmentId(), shipmentAggregate.getShipmentId());
assertEquals(shipmentCreatedEvent.getOrderId(), shipmentAggregate.getOrderId());
assertEquals(shipmentCreatedEvent.getPaymentId(), shipmentAggregate.getPaymentId());
assertEquals(shipmentCreatedEvent.getProductId(), shipmentAggregate.getProductId());
assertEquals(shipmentCreatedEvent.getQuantity(), shipmentAggregate.getQuantity());
assertEquals(shipmentCreatedEvent.getBrand(), shipmentAggregate.getBrand());
assertEquals(shipmentCreatedEvent.getBasePrice(), shipmentAggregate.getBasePrice());
assertEquals(shipmentCreatedEvent.getTax(), shipmentAggregate.getTax());
assertEquals(shipmentCreatedEvent.getTotalTax(), shipmentAggregate.getTotalTax());
assertEquals(shipmentCreatedEvent.getSubTotal(), shipmentAggregate.getSubTotal());
assertEquals(shipmentCreatedEvent.getTotal(), shipmentAggregate.getTotal());
assertEquals(shipmentCreatedEvent.getUser(), shipmentAggregate.getUser());
}

@Test
void testShipmentCreatedEventHandling() {

ShipmentAggregate shipmentAggregate = new ShipmentAggregate();
ShipmentCreatedEvent shipmentCreatedEvent = ObjectCreator.getShipmentCreatedEvent();
shipmentAggregate.on(shipmentCreatedEvent);
assertEquals(shipmentCreatedEvent.getShipmentId(), shipmentAggregate.getShipmentId());
assertEquals(shipmentCreatedEvent.getOrderId(), shipmentAggregate.getOrderId());
assertEquals(shipmentCreatedEvent.getPaymentId(), shipmentAggregate.getPaymentId());
assertEquals(shipmentCreatedEvent.getProductId(), shipmentAggregate.getProductId());
assertEquals(shipmentCreatedEvent.getBrand(), shipmentAggregate.getBrand());
assertEquals(shipmentCreatedEvent.getQuantity(), shipmentAggregate.getQuantity());
assertEquals(shipmentCreatedEvent.getBasePrice(), shipmentAggregate.getBasePrice());
assertEquals(shipmentCreatedEvent.getSubTotal(), shipmentAggregate.getSubTotal());
assertEquals(shipmentCreatedEvent.getTotal(), shipmentAggregate.getTotal());
assertEquals(shipmentCreatedEvent.getTax(), shipmentAggregate.getTax());
assertEquals(shipmentCreatedEvent.getTotalTax(), shipmentAggregate.getTotalTax());
assertEquals(shipmentCreatedEvent.getUser(), shipmentAggregate.getUser());
}

@Test
void test_negative_totalTax_throws_exception() {
CreateShipmentCommand createShipmentCommand = CreateShipmentCommand.builder()
.shipmentId("shipmentId")
.orderId("orderId")
.paymentId("paymentId")
.productId("productId")
.quantity(10)
.brand("brand")
.basePrice(100.0)
.tax(0.1f)
.totalTax(-10.0f)
.subTotal(1000.0)
.total(1100.0)
.user(ObjectCreator.getUser())
.build();

assertThrows(IllegalStateException.class, () -> {
new ShipmentAggregate(createShipmentCommand);
});
}


@Test
void testShipmentCreatedEventHandlingWithNullValues() {

ShipmentAggregate shipmentAggregate = new ShipmentAggregate();
ShipmentCreatedEvent shipmentCreatedEvent = ShipmentCreatedEvent.builder().build();

shipmentAggregate.on(shipmentCreatedEvent);

assertNull(shipmentAggregate.getShipmentId());
assertNull(shipmentAggregate.getOrderId());
assertNull(shipmentAggregate.getPaymentId());
assertNull(shipmentAggregate.getProductId());
assertNull(shipmentAggregate.getBrand());
assertNull(shipmentAggregate.getQuantity());
assertNull(shipmentAggregate.getBasePrice());
assertNull(shipmentAggregate.getSubTotal());
assertNull(shipmentAggregate.getTotal());
assertNull(shipmentAggregate.getTax());
assertNull(shipmentAggregate.getTotalTax());
assertNull(shipmentAggregate.getUser());
}

@Test
void test_instantiation_success() {
ShipmentAggregate shipmentAggregate = new ShipmentAggregate();
assertNotNull(shipmentAggregate);
}


@Test
void testShipmentAggregateConstructorWithNullShipmentId() {
CreateShipmentCommand createShipmentCommand = CreateShipmentCommand.builder()
.shipmentId(null)
.orderId("orderId")
.paymentId("paymentId")
.productId("productId")
.quantity(10)
.brand("brand")
.basePrice(100.0)
.tax(0.1f)
.totalTax(10.0f)
.subTotal(1000.0)
.total(1100.0)
.user(ObjectCreator.getUser())
.build();

assertThrows(IllegalStateException.class, () -> {
new ShipmentAggregate(createShipmentCommand);
});
}

@Test
void testShipmentAggregateConstructorWithInvalidTotalCalculation() {
CreateShipmentCommand createShipmentCommand = CreateShipmentCommand.builder()
.shipmentId("shipmentId")
.orderId("orderId")
.paymentId("paymentId")
.productId("productId")
.quantity(-1) // Invalid quantity
.brand("brand")
.basePrice(100.0)
.tax(0.1f)
.totalTax(10.0f)
.subTotal(1000.0)
.total(1100.0)
.user(ObjectCreator.getUser())
.build();

assertThrows(IllegalStateException.class, () -> {
new ShipmentAggregate(createShipmentCommand);
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.nashtech.shipment.handler;

import com.nashtech.common.event.ShipmentCreatedEvent;
import com.nashtech.shipment.entity.ShipmentEntity;
import com.nashtech.shipment.repository.ShipmentRepository;
import com.nashtech.shipment.service.PubSubPublisherService;
import com.nashtech.shipment.util.ObjectCreator;
import com.nashtech.shipment.util.TestTag;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;

@Tag(TestTag.SMALL_TESTS)
class ShipmentEventHandlerTest {

@Test
void testShipmentEventHandler() {

ShipmentRepository mockShipmentRepository = Mockito.mock(ShipmentRepository.class);
PubSubPublisherService mockPubSubPublisherService = Mockito.mock(PubSubPublisherService.class);

ShipmentEventHandler shipmentEventHandler = new ShipmentEventHandler(mockShipmentRepository, mockPubSubPublisherService);

ShipmentCreatedEvent shipmentCreatedEvent = ObjectCreator.getShipmentCreatedEvent();


shipmentEventHandler.on(shipmentCreatedEvent);


Mockito.verify(mockShipmentRepository, times(1)).save(any(ShipmentEntity.class));
Mockito.verify(mockPubSubPublisherService, times(1)).messagePublisher(any(ShipmentEntity.class));
}

}
Loading

0 comments on commit 15a65bc

Please sign in to comment.