Add SQL outbox for MAX sends
This commit is contained in:
@@ -46,7 +46,8 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
["QMax:PairingCode"] = "test-pairing",
|
||||
["QMax:JwtSecret"] = "development-only-qmax-secret-change-before-production",
|
||||
["QMax:MaxMode"] = "Mock",
|
||||
["QMax:MaxPollIntervalSeconds"] = "600"
|
||||
["QMax:MaxPollIntervalSeconds"] = "600",
|
||||
["QMax:MaxOutboxPollIntervalSeconds"] = "600"
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -89,7 +90,8 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
var message = await messageResponse.Content.ReadFromJsonAsync<MessageDto>(JsonOptions);
|
||||
Assert.NotNull(message);
|
||||
Assert.Equal("hello from test", message!.Text);
|
||||
Assert.Equal(MessageDeliveryState.Sent, message.DeliveryState);
|
||||
Assert.Null(message.ExternalId);
|
||||
Assert.Equal(MessageDeliveryState.Sending, message.DeliveryState);
|
||||
|
||||
var replyResponse = await client.PostAsJsonAsync(
|
||||
$"/api/chats/{chat.Id}/messages",
|
||||
@@ -98,6 +100,14 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
var reply = await replyResponse.Content.ReadFromJsonAsync<MessageDto>(JsonOptions);
|
||||
Assert.NotNull(reply);
|
||||
Assert.Equal(message.Id, reply!.ReplyToMessageId);
|
||||
|
||||
Assert.True(await ProcessOutboxAsync(_factory) >= 2);
|
||||
var sentMessages = await client.GetFromJsonAsync<MessageDto[]>($"/api/chats/{chat.Id}/messages", JsonOptions);
|
||||
Assert.All(sentMessages!, sent =>
|
||||
{
|
||||
Assert.False(string.IsNullOrWhiteSpace(sent.ExternalId));
|
||||
Assert.Equal(MessageDeliveryState.Sent, sent.DeliveryState);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -248,7 +258,7 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UploadAttachmentFlowStoresDownloadsAndMarksSent()
|
||||
public async Task UploadAttachmentFlowStoresDownloadsAndQueuesSend()
|
||||
{
|
||||
using var client = _factory.CreateClient();
|
||||
await LoginAsync(client);
|
||||
@@ -278,7 +288,8 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
await AssertStatusAsync(HttpStatusCode.OK, upload);
|
||||
var message = await upload.Content.ReadFromJsonAsync<MessageDto>(JsonOptions);
|
||||
Assert.NotNull(message);
|
||||
Assert.Equal(MessageDeliveryState.Sent, message!.DeliveryState);
|
||||
Assert.Equal(MessageDeliveryState.Sending, message!.DeliveryState);
|
||||
Assert.Null(message.ExternalId);
|
||||
Assert.Equal(baseMessage.Id, message.ReplyToMessageId);
|
||||
var attachment = Assert.Single(message.Attachments);
|
||||
Assert.Equal(AttachmentKind.Image, attachment.Kind);
|
||||
@@ -320,7 +331,8 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
await AssertStatusAsync(HttpStatusCode.OK, upload);
|
||||
var sent = await upload.Content.ReadFromJsonAsync<MessageDto>(JsonOptions);
|
||||
Assert.NotNull(sent);
|
||||
Assert.Equal(bridge.MessageExternalId, sent!.ExternalId);
|
||||
Assert.Null(sent!.ExternalId);
|
||||
Assert.Equal(MessageDeliveryState.Sending, sent.DeliveryState);
|
||||
Assert.Single(sent.Attachments);
|
||||
|
||||
var sync = await client.PostAsync("/api/max/sync", null);
|
||||
@@ -328,6 +340,7 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
|
||||
var messages = await client.GetFromJsonAsync<MessageDto[]>($"/api/chats/{chat.Id}/messages", JsonOptions);
|
||||
var echoed = Assert.Single(messages!, message => message.ExternalId == bridge.MessageExternalId);
|
||||
Assert.Equal(sent.Id, echoed.Id);
|
||||
var attachment = Assert.Single(echoed.Attachments);
|
||||
Assert.Equal("local-photo.png", attachment.FileName);
|
||||
|
||||
@@ -367,7 +380,7 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
Assert.Equal(target.Id, forwardedText!.ChatId);
|
||||
Assert.Equal("forward text marker", forwardedText.Text);
|
||||
Assert.Equal("You", forwardedText.ForwardedFrom);
|
||||
Assert.Equal(MessageDeliveryState.Sent, forwardedText.DeliveryState);
|
||||
Assert.Equal(MessageDeliveryState.Sending, forwardedText.DeliveryState);
|
||||
|
||||
var bytes = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x44, 0x41, 0x54, 0x41 };
|
||||
using var form = new MultipartFormDataContent();
|
||||
@@ -389,7 +402,7 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
Assert.Equal(target.Id, forwardedAttachmentMessage!.ChatId);
|
||||
Assert.Equal("forward caption", forwardedAttachmentMessage.Text);
|
||||
Assert.Equal("You", forwardedAttachmentMessage.ForwardedFrom);
|
||||
Assert.Equal(MessageDeliveryState.Sent, forwardedAttachmentMessage.DeliveryState);
|
||||
Assert.Equal(MessageDeliveryState.Sending, forwardedAttachmentMessage.DeliveryState);
|
||||
var forwardedAttachment = Assert.Single(forwardedAttachmentMessage.Attachments);
|
||||
Assert.Equal("forward.png", forwardedAttachment.FileName);
|
||||
Assert.Equal(AttachmentKind.Image, forwardedAttachment.Kind);
|
||||
@@ -518,7 +531,12 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
await AssertStatusAsync(HttpStatusCode.OK, messageResponse);
|
||||
var message = await messageResponse.Content.ReadFromJsonAsync<MessageDto>(JsonOptions);
|
||||
Assert.NotNull(message);
|
||||
|
||||
Assert.True(await ProcessOutboxAsync(factory) >= 1);
|
||||
var afterOutbox = await client.GetFromJsonAsync<MessageDto[]>($"/api/chats/{chat.Id}/messages", JsonOptions);
|
||||
message = Assert.Single(afterOutbox!, x => x.Id == message!.Id);
|
||||
Assert.False(string.IsNullOrWhiteSpace(message!.ExternalId));
|
||||
Assert.Equal(MessageDeliveryState.Sent, message.DeliveryState);
|
||||
|
||||
using var editRequest = new HttpRequestMessage(HttpMethod.Patch, $"/api/chats/{chat.Id}/messages/{message.Id}")
|
||||
{
|
||||
@@ -549,7 +567,7 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FailedMaxTextSendDoesNotCreateLocalMessage()
|
||||
public async Task FailedMaxTextSendMarksLocalMessageFailed()
|
||||
{
|
||||
using var factory = _factory.WithWebHostBuilder(builder =>
|
||||
{
|
||||
@@ -566,16 +584,23 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
var response = await client.PostAsJsonAsync(
|
||||
$"/api/chats/{chat.Id}/messages",
|
||||
new SendMessageRequest("this must not be stored"));
|
||||
await AssertStatusAsync(HttpStatusCode.BadGateway, response);
|
||||
await AssertStatusAsync(HttpStatusCode.OK, response);
|
||||
var queued = await response.Content.ReadFromJsonAsync<MessageDto>(JsonOptions);
|
||||
Assert.NotNull(queued);
|
||||
Assert.Equal(MessageDeliveryState.Sending, queued!.DeliveryState);
|
||||
|
||||
Assert.True(await ProcessOutboxAsync(factory) >= 1);
|
||||
var messages = await client.GetFromJsonAsync<MessageDto[]>($"/api/chats/{chat.Id}/messages", JsonOptions);
|
||||
Assert.NotNull(messages);
|
||||
Assert.Empty(messages!);
|
||||
var failed = Assert.Single(messages!, x => x.Id == queued.Id);
|
||||
Assert.Equal(queued.Id, failed.Id);
|
||||
Assert.Equal(MessageDeliveryState.Failed, failed.DeliveryState);
|
||||
Assert.Contains("Simulated MAX send failure.", failed.Error);
|
||||
|
||||
var chats = await client.GetFromJsonAsync<ChatDto[]>("/api/chats", JsonOptions);
|
||||
var updated = Assert.Single(chats!, x => x.Id == chat.Id);
|
||||
Assert.Null(updated.LastMessagePreview);
|
||||
Assert.Null(updated.LastMessageAt);
|
||||
Assert.Equal("this must not be stored", updated.LastMessagePreview);
|
||||
Assert.NotNull(updated.LastMessageAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -1077,6 +1102,13 @@ public sealed class ApiSmokeTests : IDisposable
|
||||
return chat!;
|
||||
}
|
||||
|
||||
private static async Task<int> ProcessOutboxAsync(WebApplicationFactory<Program> factory)
|
||||
{
|
||||
await using var scope = factory.Services.CreateAsyncScope();
|
||||
var outbox = scope.ServiceProvider.GetRequiredService<MaxOutboxService>();
|
||||
return await outbox.ProcessOnceAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
private static async Task AssertStatusAsync(HttpStatusCode expected, HttpResponseMessage response)
|
||||
{
|
||||
if (response.StatusCode != expected)
|
||||
|
||||
Reference in New Issue
Block a user