Skip to content

Commit

Permalink
Chore/improve amqp test coverage (#948)
Browse files Browse the repository at this point in the history
* tests: refactoring + add yaml endpoint testing

* tests: additional RabbitListener tests

* chore(amqp): fix queue configuration

* chore(amqp): simplify local testing

* chore: update asyncapi.yaml gradle script

* feat(amqp): scan all queues

In addition to the routingKeys

* test(amqp): update asyncapi artifacts

Part of GH-366

* test(ui): use valid mock data

* chore(ui): fix formatting

* test(amqp): update asyncapi artifacts

* feat(ui): show channel bindings

* test: add WaitStrategy for ApiSystemTest

* chore(amqp): use non-exclusive queue in example

* chore(amqp): use non-exclusive queue in example

* test(amqp): persist patched asyncapi.yaml

* chore(amqp): add spring-messaging dependency in example

* test(amqp): wait for ready amqp server

* test(amqp): update e2e

* test(amqp): cleanup

---------

Co-authored-by: Pascal Dal Farra <[email protected]>
  • Loading branch information
timonback and Pascal Dal Farra authored Sep 13, 2024
1 parent 59dfa3b commit 6e18922
Show file tree
Hide file tree
Showing 34 changed files with 1,007 additions and 41 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ allprojects {
project
.file('springwolf-examples/' + it + '-example/src/test/resources/asyncapi.actual.json')
.renameTo('springwolf-examples/' + it + '-example/src/test/resources/asyncapi.json')
project
.file('springwolf-examples/' + it + '-example/src/test/resources/asyncapi.actual.yaml')
.renameTo('springwolf-examples/' + it + '-example/src/test/resources/asyncapi.yaml')
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ public class SpringwolfInitApplicationListener implements ApplicationListener<Ap
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if (configProperties.getInitMode() == InitMode.BACKGROUND) {
log.debug("triggering background asyncapi creation..");
log.debug("Triggering background asyncapi creation");
new Thread(asyncApiService::getAsyncAPI).start();
} else {
log.debug("triggering asyncapi creation..");
log.debug("Triggering asyncapi creation");
this.asyncApiService.getAsyncAPI();
}
}
Expand Down
2 changes: 2 additions & 0 deletions springwolf-examples/e2e/tests/publishing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ function testPublishingEveryChannelItem() {
payload === "MonetaryAmount" || // Issue with either MonetaryAmount of ModelConverters
payload === "Message" || // Unable to instantiate ExamplePayloadProtobufDto$Message class
payload === "VehicleBase" || // Unable to publish abstract class for discriminator demo
payload === "GenericPayloadDto" || // Unable to publish generic payload (amqp)
channelName === "#" || // Publishing through amqp exchange is not supported, see GH-366
channelName === "example-topic-routing-key" // Publishing through amqp exchange is not supported, see GH-366
) {
return; // skip
Expand Down
1 change: 1 addition & 0 deletions springwolf-examples/springwolf-amqp-example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
implementation "org.springframework.boot:spring-boot-autoconfigure"
implementation "org.springframework.boot:spring-boot"
implementation "org.springframework:spring-context"
implementation "org.springframework:spring-messaging"

testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
services:
app:
image: stavshamir/springwolf-amqp-example:${SPRINGWOLF_VERSION}
environment:
AMQP_HOST: amqp
links:
- amqp
ports:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.examples.amqp;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class AmqpConstants {

// Exchanges
public static final String EXCHANGE_EXAMPLE_TOPIC_EXCHANGE = "example-topic-exchange";
public static final String EXCHANGE_CRUD_TOPIC_EXCHANGE_1 = "CRUD-topic-exchange-1";
public static final String EXCHANGE_CRUD_TOPIC_EXCHANGE_2 = "CRUD-topic-exchange-2";
/**
* Direct Exchange - Routing is based on 'Routing key'.
* Messages are 'routed' towards queue which name is equals to 'routing key' <BR/>
* Note:<BR/>
* The 'Default exchange' is a 'Direct exchange' with an empty name ( name= "")
*/
public static final String EXCHANGE_DEFAULT_EXCHANGE = "";

// Routing keys
/**
* When a queue is bound with "#" (hash) binding key,
* it will receive all the messages, regardless of the routing key - like in fanout exchange.
*/
public static final String ROUTING_KEY_ALL_MESSAGES = "#";

public static final String ROUTING_KEY_EXAMPLE_TOPIC_ROUTING_KEY = "example-topic-routing-key";

// Queues
public static final String QUEUE_EXAMPLE_QUEUE = "example-queue";
public static final String QUEUE_ANOTHER_QUEUE = "another-queue";
public static final String QUEUE_MULTI_PAYLOAD_QUEUE = "multi-payload-queue";
public static final String QUEUE_EXAMPLE_BINDINGS_QUEUE = "example-bindings-queue";

public static final String QUEUE_CREATE = "queue-create";
public static final String QUEUE_READ = "queue-read";
public static final String QUEUE_DELETE = "queue-delete";
public static final String QUEUE_UPDATE = "queue-update";
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.examples.amqp.configuration;

import io.github.springwolf.examples.amqp.AmqpConstants;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
Expand All @@ -23,34 +24,39 @@ public Jackson2JsonMessageConverter converter() {

@Bean
public Queue exampleQueue() {
return new Queue("example-queue", false);
return new Queue(AmqpConstants.QUEUE_EXAMPLE_QUEUE, false);
}

@Bean
public Queue anotherQueue() {
return new Queue("another-queue", false);
return new Queue(AmqpConstants.QUEUE_ANOTHER_QUEUE, false);
}

@Bean
public Queue exampleBindingsQueue() {
return new Queue("example-bindings-queue", false, true, true);
return new Queue(AmqpConstants.QUEUE_EXAMPLE_BINDINGS_QUEUE, false, false, true);
}

@Bean
public Queue queueRead() {
return new Queue(AmqpConstants.QUEUE_READ, false);
}

@Bean
public Exchange exampleTopicExchange() {
return new TopicExchange("example-topic-exchange");
return new TopicExchange(AmqpConstants.EXCHANGE_EXAMPLE_TOPIC_EXCHANGE);
}

@Bean
public Queue multiPayloadQueue() {
return new Queue("multi-payload-queue");
return new Queue(AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE);
}

@Bean
public Binding exampleTopicBinding(Queue exampleBindingsQueue, Exchange exampleTopicExchange) {
return BindingBuilder.bind(exampleBindingsQueue)
.to(exampleTopicExchange)
.with("example-topic-routing-key")
.with(AmqpConstants.ROUTING_KEY_EXAMPLE_TOPIC_ROUTING_KEY)
.noargs();
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.examples.amqp.consumers;

import io.github.springwolf.examples.amqp.AmqpConstants;
import io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto;
import io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto;
import io.github.springwolf.examples.amqp.dtos.GenericPayloadDto;
import io.github.springwolf.examples.amqp.producers.AnotherProducer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

@Component
Expand All @@ -20,9 +24,9 @@ public class ExampleConsumer {

private final AnotherProducer anotherProducer;

@RabbitListener(queues = "example-queue")
@RabbitListener(queues = AmqpConstants.QUEUE_EXAMPLE_QUEUE)
public void receiveExamplePayload(ExamplePayloadDto payload) {
log.info("Received new message in example-queue: {}", payload.toString());
log.info("Received new message in {}: {}", AmqpConstants.QUEUE_EXAMPLE_QUEUE, payload.toString());

AnotherPayloadDto example = new AnotherPayloadDto();
example.setExample(payload);
Expand All @@ -31,37 +35,102 @@ public void receiveExamplePayload(ExamplePayloadDto payload) {
anotherProducer.sendMessage(example);
}

@RabbitListener(queues = "another-queue")
@RabbitListener(queues = AmqpConstants.QUEUE_ANOTHER_QUEUE)
public void receiveAnotherPayload(AnotherPayloadDto payload) {
log.info("Received new message in another-queue: {}", payload.toString());
log.info("Received new message in {}: {}", AmqpConstants.QUEUE_ANOTHER_QUEUE, payload.toString());
}

@RabbitListener(
bindings = {
@QueueBinding(
exchange = @Exchange(name = "example-topic-exchange", type = ExchangeTypes.TOPIC),
exchange =
@Exchange(
name = AmqpConstants.EXCHANGE_EXAMPLE_TOPIC_EXCHANGE,
type = ExchangeTypes.TOPIC),
value =
@Queue(
name = "example-bindings-queue",
name = AmqpConstants.QUEUE_EXAMPLE_BINDINGS_QUEUE,
durable = "false",
exclusive = "true",
exclusive = "false",
autoDelete = "true"),
key = "example-topic-routing-key")
key = AmqpConstants.ROUTING_KEY_EXAMPLE_TOPIC_ROUTING_KEY)
})
public void bindingsExample(AnotherPayloadDto payload) {
log.info(
"Received new message in example-bindings-queue"
+ " through exchange example-topic-exchange using routing key example-topic-routing-key: {}",
"Received new message in {}" + " through exchange {}" + " using routing key {}: {}",
AmqpConstants.QUEUE_EXAMPLE_BINDINGS_QUEUE,
AmqpConstants.EXCHANGE_EXAMPLE_TOPIC_EXCHANGE,
AmqpConstants.ROUTING_KEY_EXAMPLE_TOPIC_ROUTING_KEY,
payload.toString());
}

@RabbitListener(queues = "multi-payload-queue")
@RabbitListener(queues = AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE)
public void bindingsBeanExample(AnotherPayloadDto payload) {
log.info("Received new message in multi-payload-queue (AnotherPayloadDto): {}", payload.toString());
log.info(
"Received new message in {} (AnotherPayloadDto): {}",
AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE,
payload.toString());
}

@RabbitListener(queues = "multi-payload-queue")
@RabbitListener(queues = AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE)
public void bindingsBeanExample(ExamplePayloadDto payload) {
log.info("Received new message in multi-payload-queue (ExamplePayloadDto): {}", payload.toString());
log.info(
"Received new message in {} (ExamplePayloadDto): {}",
AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE,
payload.toString());
}

@RabbitListener(queuesToDeclare = @Queue(name = AmqpConstants.QUEUE_CREATE, autoDelete = "false", durable = "true"))
public void queuesToDeclareCreate(Message message, @Payload GenericPayloadDto<String> payload) {
log.info(
"Received new message {} in {} (GenericPayloadDto<String>): {}",
message,
AmqpConstants.QUEUE_CREATE,
payload.toString());
}

@RabbitListener(queuesToDeclare = @Queue(name = AmqpConstants.QUEUE_DELETE, autoDelete = "false", durable = "true"))
public void queuesToDeclareDelete(Message message, @Payload GenericPayloadDto<Long> payload) {
log.info(
"Received new message {} in {} (GenericPayloadDto<Long>): {}",
message,
AmqpConstants.QUEUE_DELETE,
payload.toString());
}

@RabbitListener(
autoStartup = "false",
bindings =
@QueueBinding(
exchange =
@Exchange(
name = AmqpConstants.EXCHANGE_CRUD_TOPIC_EXCHANGE_1,
type = ExchangeTypes.TOPIC),
key = AmqpConstants.ROUTING_KEY_ALL_MESSAGES,
value = @Queue(name = AmqpConstants.QUEUE_UPDATE, durable = "true", autoDelete = "false")))
public void bindingsUpdate(Message message, @Payload GenericPayloadDto<ExamplePayloadDto> payload) {
log.info(
"Received new message {} in {} (GenericPayloadDto<ExamplePayloadDto>): {}",
message,
AmqpConstants.QUEUE_UPDATE,
payload.toString());
}

@RabbitListener(
autoStartup = "false",
bindings =
@QueueBinding(
exchange =
@Exchange(
name = AmqpConstants.EXCHANGE_CRUD_TOPIC_EXCHANGE_2,
type = ExchangeTypes.TOPIC),
key = AmqpConstants.ROUTING_KEY_ALL_MESSAGES,
value = @Queue(name = AmqpConstants.QUEUE_READ, durable = "false", autoDelete = "false")))
public void bindingsRead(Message message, @Payload ExamplePayloadDto payload) {
log.info(
"Received new message {} in {} (ExamplePayloadDto): {}",
message,
AmqpConstants.QUEUE_UPDATE,
payload.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.examples.amqp.dtos;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

@Schema(description = "Generic payload model")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GenericPayloadDto<T> {

@Schema(description = "Generic Payload field", requiredMode = REQUIRED)
private T genericValue;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.github.springwolf.bindings.amqp.annotations.AmqpAsyncOperationBinding;
import io.github.springwolf.core.asyncapi.annotations.AsyncOperation;
import io.github.springwolf.core.asyncapi.annotations.AsyncPublisher;
import io.github.springwolf.examples.amqp.AmqpConstants;
import io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
Expand All @@ -17,11 +18,14 @@ public class AnotherProducer {
@AsyncPublisher(
operation =
@AsyncOperation(
channelName = "example-topic-exchange",
channelName = AmqpConstants.EXCHANGE_EXAMPLE_TOPIC_EXCHANGE,
description = "Custom, optional description defined in the AsyncPublisher annotation"))
@AmqpAsyncOperationBinding()
public void sendMessage(AnotherPayloadDto msg) {
// send
rabbitTemplate.convertAndSend("example-topic-exchange", "example-topic-routing-key", msg);
rabbitTemplate.convertAndSend(
AmqpConstants.EXCHANGE_EXAMPLE_TOPIC_EXCHANGE,
AmqpConstants.ROUTING_KEY_EXAMPLE_TOPIC_ROUTING_KEY,
msg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ spring.application.name=Springwolf example project - AMQP

#########
# Spring amqp configuration
spring.rabbitmq.host=amqp
spring.rabbitmq.host=${AMQP_HOST:localhost}
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
Expand Down
Loading

0 comments on commit 6e18922

Please sign in to comment.