Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ public MediatorRuntime Build(IServiceProvider applicationServices)
config.Kind,
MediatorUrn.Message(serviceName, messageName),
messageName,
config.ResponseType is { } responseType ? MediatorDescriptionHelpers.GetTypeName(responseType) : null));
config.ResponseType is { } responseType ? MediatorDescriptionHelpers.GetTypeName(responseType) : null,
config.Source));

var terminal = config.Delegate ?? BuildPipelineViaReflection(config);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ namespace Mocha.Mediator;
/// <param name="MessageId">The URN identity of the message the handler handles.</param>
/// <param name="MessageName">The readable message type name.</param>
/// <param name="ResponseTypeName">The readable response type name, or <c>null</c> for void commands and notifications.</param>
/// <param name="Source">Source metadata captured from the handler declaration, or <c>null</c> when not provided.</param>
public sealed record MediatorHandlerDescription(
string Id,
string Name,
MediatorHandlerKind Kind,
string MessageId,
string MessageName,
string? ResponseTypeName);
string? ResponseTypeName,
SourceMetadata? Source);
Comment thread
PascalSenn marked this conversation as resolved.
3 changes: 2 additions & 1 deletion src/Mocha/src/Mocha/Consumers/Consumer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ public virtual ConsumerDescription Describe()
DescriptionHelpers.GetTypeName(Identity),
Identity.FullName,
null,
false);
false,
Configuration?.Source);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,14 @@ private async Task ProcessBatchAsync(MessageBatch<TEvent> batch, CancellationTok

public override ConsumerDescription Describe()
{
return new ConsumerDescription(Urn, Name, DescriptionHelpers.GetTypeName(Identity), Identity.FullName, null, true);
return new ConsumerDescription(
Urn,
Name,
DescriptionHelpers.GetTypeName(Identity),
Identity.FullName,
null,
true,
Configuration?.Source);
}

public override async ValueTask DisposeAsync()
Expand Down
4 changes: 3 additions & 1 deletion src/Mocha/src/Mocha/Descriptions/ConsumerDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ namespace Mocha;
/// <param name="IdentityTypeFullName">The fully qualified type name of the handler, or <c>null</c> if unavailable.</param>
/// <param name="SagaName">The name of the associated saga, or <c>null</c> if this consumer is not part of a saga.</param>
/// <param name="IsBatch">Whether this consumer processes messages in batches.</param>
/// <param name="Source">Source metadata captured from the consumer declaration, or <c>null</c> when not provided.</param>
public sealed record ConsumerDescription(
string Id,
string Name,
string IdentityType,
string? IdentityTypeFullName,
string? SagaName,
bool IsBatch);
bool IsBatch,
SourceMetadata? Source);
Comment thread
PascalSenn marked this conversation as resolved.
4 changes: 3 additions & 1 deletion src/Mocha/src/Mocha/Descriptions/MessageTypeDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Mocha;
/// <param name="IsInternal">Whether this message type is internal to the bus and not exposed to user code.</param>
/// <param name="DefaultContentType">The default serialization content type, or <c>null</c> if using the bus default.</param>
/// <param name="EnclosedMessageIdentities">The identities of message types enclosed by this type, or <c>null</c> if none.</param>
/// <param name="Source">Source metadata captured from the message type declaration, or <c>null</c> when not provided.</param>
public sealed record MessageTypeDescription(
string Id,
string Identity,
Expand All @@ -19,4 +20,5 @@ public sealed record MessageTypeDescription(
bool IsInterface,
bool IsInternal,
string? DefaultContentType,
IReadOnlyList<string>? EnclosedMessageIdentities);
IReadOnlyList<string>? EnclosedMessageIdentities,
SourceMetadata? Source);
Comment thread
PascalSenn marked this conversation as resolved.
4 changes: 3 additions & 1 deletion src/Mocha/src/Mocha/Descriptions/SagaDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ namespace Mocha;
/// <param name="StateTypeFullName">The fully qualified type name of the saga state, or <c>null</c> if unavailable.</param>
/// <param name="ConsumerName">The name of the consumer that drives this saga.</param>
/// <param name="States">The descriptions of all states in this saga.</param>
/// <param name="Source">Source metadata captured from the saga declaration, or <c>null</c> when not provided.</param>
public sealed record SagaDescription(
string Id,
string Name,
string StateType,
string? StateTypeFullName,
string ConsumerName,
IReadOnlyList<SagaStateDescription> States);
IReadOnlyList<SagaStateDescription> States,
SourceMetadata? Source);
Comment thread
PascalSenn marked this conversation as resolved.
3 changes: 2 additions & 1 deletion src/Mocha/src/Mocha/MessageTypes/MessageType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,8 @@ public MessageTypeDescription Describe()
IsInterface,
IsInternal,
DefaultContentType?.Value,
EnclosedMessageIdentities.IsDefaultOrEmpty ? null : EnclosedMessageIdentities);
EnclosedMessageIdentities.IsDefaultOrEmpty ? null : EnclosedMessageIdentities,
Source);
}

[RequiresUnreferencedCode("Uses GetInterfaces and BaseType traversal which may reference trimmed types.")]
Expand Down
3 changes: 2 additions & 1 deletion src/Mocha/src/Mocha/Sagas/Saga.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ state.Response is not null
DescriptionHelpers.GetTypeName(StateType),
StateType.FullName,
Consumer.Name,
states);
states,
Configuration?.Source);
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Mocha/src/Mocha/Sagas/SagaEventListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ public override ConsumerDescription Describe()
DescriptionHelpers.GetTypeName(Identity),
Identity.FullName,
saga.Name,
false);
false,
Configuration?.Source ?? saga.Configuration?.Source);
}

/// <inheritdoc />
Expand Down
26 changes: 26 additions & 0 deletions src/Mocha/test/Mocha.Mediator.Tests/MediatorDescriptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,30 @@ public void Describe_Should_ReturnMediatorDescription_When_HandlersRegistered()
// assert
description.MatchSnapshot();
}

[Fact]
public void Describe_Should_ReturnSource_When_HandlerConfigurationHasSource()
{
// arrange
var source = new SourceMetadata
{
Assembly = "Mocha.Mediator.Tests",
RepositoryUrl = "https://github.com/example/mocha",
Commit = "abc123",
XmlDocumentation = "<summary>Manual query handler.</summary>",
DeclarationLocation = new DeclarationLocation("ManualQueryHandler.cs", 1, 1, 5, 2)
};
var services = new ServiceCollection();
services.AddMediator()
.ConfigureOptions(o => o.ServiceName = "orders")
.AddHandler<ManualQueryHandler>(d => d.Extend().Configuration.Source = source);
using var provider = services.BuildServiceProvider();

// act
var description = provider.GetRequiredService<MediatorRuntime>().Describe();

// assert
var handler = description.Handlers.Single(h => h.Name == nameof(ManualQueryHandler));
Assert.Same(source, handler.Source);
Comment thread
PascalSenn marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,35 @@
"Kind": "CommandResponse",
"MessageId": "urn:mocha:svc:orders:mediator:message:ManualCommand",
"MessageName": "ManualCommand",
"ResponseTypeName": "ManualResponse"
"ResponseTypeName": "ManualResponse",
"Source": null
},
{
"Id": "urn:mocha:svc:orders:mediator:handler:ManualNotificationHandler1",
"Name": "ManualNotificationHandler1",
"Kind": "Notification",
"MessageId": "urn:mocha:svc:orders:mediator:message:ManualNotification",
"MessageName": "ManualNotification",
"ResponseTypeName": null
"ResponseTypeName": null,
"Source": null
},
{
"Id": "urn:mocha:svc:orders:mediator:handler:ManualQueryHandler",
"Name": "ManualQueryHandler",
"Kind": "Query",
"MessageId": "urn:mocha:svc:orders:mediator:message:ManualQuery",
"MessageName": "ManualQuery",
"ResponseTypeName": "ManualResponse"
"ResponseTypeName": "ManualResponse",
"Source": null
},
{
"Id": "urn:mocha:svc:orders:mediator:handler:ManualVoidCommandHandler",
"Name": "ManualVoidCommandHandler",
"Kind": "Command",
"MessageId": "urn:mocha:svc:orders:mediator:message:ManualVoidCommand",
"MessageName": "ManualVoidCommand",
"ResponseTypeName": null
"ResponseTypeName": null,
"Source": null
}
]
}
64 changes: 64 additions & 0 deletions src/Mocha/test/Mocha.Sagas.Tests/SagaInitializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,70 @@ public void Describe_Should_UseStateAndTransitionUrns_When_SagaInitialized()
Assert.Equal(startedCancel.Urn, startedCancelDescription.Id);
}

[Fact]
public void Describe_Should_ReturnSource_When_SagaConfigurationHasSource()
{
// Arrange
var source = new SourceMetadata
{
Assembly = "Mocha.Sagas.Tests",
RepositoryUrl = "https://github.com/example/mocha",
Commit = "abc123",
XmlDocumentation = "<summary>Test saga.</summary>",
DeclarationLocation = new DeclarationLocation("TestSaga.cs", 1, 1, 10, 2)
};
var saga = CreateInitializedSagaWithSource(source);

// Act
var description = saga.Describe();

// Assert
Assert.Same(source, description.Source);
Comment thread
PascalSenn marked this conversation as resolved.
}

[Fact]
public void ConsumerDescribe_Should_FallBackToSagaSource_When_ConsumerConfigurationHasNoSource()
{
// Arrange - the saga consumer never sets Source on its own ConsumerConfiguration, so
// Consumer.Describe() must fall back to the saga's own Source.
var source = new SourceMetadata
{
Assembly = "Mocha.Sagas.Tests",
RepositoryUrl = "https://github.com/example/mocha",
Commit = "abc123",
XmlDocumentation = "<summary>Test saga.</summary>",
DeclarationLocation = new DeclarationLocation("TestSaga.cs", 1, 1, 10, 2)
};
var saga = CreateInitializedSagaWithSource(source);

// Act
var description = saga.Consumer.Describe();

// Assert
Assert.Same(source, description.Source);
Comment thread
PascalSenn marked this conversation as resolved.
}

private Saga<TestState> CreateInitializedSagaWithSource(SourceMetadata source)
{
var saga =
Saga.Create<TestState>(descriptor =>
{
descriptor.Extend().Configuration.Source = source;

descriptor
.Initially()
.OnEvent<StartEvent>()
.TransitionTo("Started")
.StateFactory(s => new TestState(Guid.NewGuid(), "Started"));
descriptor.During("Started").OnEvent<TriggerEvent>().TransitionTo("Success");
descriptor.Finally("Success");
});

saga.Initialize(_context);

return saga;
}

private Saga<TestState> CreateInitializedSagaWithCancelTransitions()
{
var saga =
Expand Down
23 changes: 23 additions & 0 deletions src/Mocha/test/Mocha.Tests/Consumers/ConsumerBehaviorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ public void Describe_Should_ReturnConsumerDescription_When_EventHandlerRegistere
description.MatchSnapshot();
}

[Fact]
public void Describe_Should_ReturnSource_When_ConsumerConfigurationHasSource()
{
// arrange
var source = new SourceMetadata
{
Assembly = "Mocha.Tests",
RepositoryUrl = "https://github.com/example/mocha",
Commit = "abc123",
XmlDocumentation = "<summary>Handles order created events.</summary>",
DeclarationLocation = new DeclarationLocation("OrderCreatedHandler.cs", 1, 1, 5, 2)
};
var runtime = CreateRuntime(
b => b.AddEventHandler<OrderCreatedHandler>(d => d.Extend().Configuration.Source = source));
var consumer = runtime.Consumers.First(c => c.Name == nameof(OrderCreatedHandler));

// act
var description = consumer.Describe();

// assert
Assert.Same(source, description.Source);
Comment thread
PascalSenn marked this conversation as resolved.
}

[Fact]
public async Task ProcessAsync_Should_ThrowInvalidOperationException_When_ContextIsNotConsumeContext()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"IdentityType": "OrderCreatedHandler",
"IdentityTypeFullName": "Mocha.Tests.Consumers.ConsumerBehaviorTests+OrderCreatedHandler",
"SagaName": null,
"IsBatch": false
"IsBatch": false,
"Source": null
}
27 changes: 27 additions & 0 deletions src/Mocha/test/Mocha.Tests/MessageTypes/MessageTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,33 @@ public void EventHandler_Should_RegisterMessageType_When_AddingEventHandler()
Assert.Equal(typeof(OrderCreated), messageType.RuntimeType);
}

[Fact]
public void Describe_Should_ReturnSource_When_MessageTypeConfigurationHasSource()
{
// arrange
var source = new SourceMetadata
{
Assembly = "Mocha.Tests",
RepositoryUrl = "https://github.com/example/mocha",
Commit = "abc123",
XmlDocumentation = "<summary>Raised when an order is created.</summary>",
DeclarationLocation = new DeclarationLocation("OrderCreated.cs", 1, 1, 3, 2)
};
var runtime = CreateRuntime(b =>
{
b.AddEventHandler<OrderCreatedHandler>();
b.AddMessage<OrderCreated>(d => d.Extend().Configuration.Source = source);
});
var messageType = runtime.Messages.GetMessageType(typeof(OrderCreated));
Assert.NotNull(messageType);

// act
var description = messageType.Describe();

// assert
Assert.Same(source, description.Source);
Comment thread
PascalSenn marked this conversation as resolved.
}

[Fact]
public void EventHandlerMessageType_Should_HaveUrnIdentity_When_Registered()
{
Expand Down
Loading