1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-23 20:28:55 -05:00

Merge branch 'main' into ac/pm-15160/create-providerclientorganizationsignup-command

This commit is contained in:
Rui Tomé 2025-06-23 11:10:08 +01:00 committed by GitHub
commit 85988fe76f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 77 additions and 127 deletions

View File

@ -4,6 +4,9 @@ on:
workflow_call: workflow_call:
pull_request: pull_request:
types: [labeled, unlabeled, opened, reopened, synchronize] types: [labeled, unlabeled, opened, reopened, synchronize]
permissions: {}
jobs: jobs:
enforce-label: enforce-label:
if: ${{ contains(github.event.*.labels.*.name, 'hold') || contains(github.event.*.labels.*.name, 'needs-qa') || contains(github.event.*.labels.*.name, 'DB-migrations-changed') || contains(github.event.*.labels.*.name, 'ephemeral-environment') }} if: ${{ contains(github.event.*.labels.*.name, 'hold') || contains(github.event.*.labels.*.name, 'needs-qa') || contains(github.event.*.labels.*.name, 'DB-migrations-changed') || contains(github.event.*.labels.*.name, 'ephemeral-environment') }}

View File

@ -16,6 +16,9 @@ jobs:
changed-files: changed-files:
name: Check for file changes name: Check for file changes
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions:
contents: read
pull-requests: write
outputs: outputs:
changes: ${{steps.check-changes.outputs.changes_detected}} changes: ${{steps.check-changes.outputs.changes_detected}}

View File

@ -22,6 +22,8 @@ on:
required: false required: false
type: string type: string
permissions: {}
jobs: jobs:
setup: setup:
name: Setup name: Setup
@ -44,49 +46,11 @@ jobs:
echo "branch=$BRANCH" >> $GITHUB_OUTPUT echo "branch=$BRANCH" >> $GITHUB_OUTPUT
cut_branch:
name: Cut branch
if: ${{ needs.setup.outputs.branch != 'none' }}
needs: setup
runs-on: ubuntu-24.04
steps:
- name: Generate GH App token
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
id: app-token
with:
app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Check out target ref
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ inputs.target_ref }}
token: ${{ steps.app-token.outputs.token }}
- name: Check if ${{ needs.setup.outputs.branch }} branch exists
env:
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then
echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Cut branch
env:
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
git switch --quiet --create $BRANCH_NAME
git push --quiet --set-upstream origin $BRANCH_NAME
bump_version: bump_version:
name: Bump Version name: Bump Version
if: ${{ always() }} if: ${{ always() }}
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- cut_branch
- setup - setup
outputs: outputs:
version: ${{ steps.set-final-version-output.outputs.version }} version: ${{ steps.set-final-version-output.outputs.version }}
@ -187,14 +151,13 @@ jobs:
- name: Push changes - name: Push changes
run: git push run: git push
cut_branch:
cherry_pick: name: Cut branch
name: Cherry-Pick Commit(s)
if: ${{ needs.setup.outputs.branch != 'none' }} if: ${{ needs.setup.outputs.branch != 'none' }}
runs-on: ubuntu-24.04
needs: needs:
- bump_version
- setup - setup
- bump_version
runs-on: ubuntu-24.04
steps: steps:
- name: Generate GH App token - name: Generate GH App token
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
@ -203,78 +166,30 @@ jobs:
app-id: ${{ secrets.BW_GHAPP_ID }} app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }} private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Check out main branch - name: Check out target ref
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with: with:
fetch-depth: 0 ref: ${{ inputs.target_ref }}
ref: main
token: ${{ steps.app-token.outputs.token }} token: ${{ steps.app-token.outputs.token }}
- name: Configure Git - name: Check if ${{ needs.setup.outputs.branch }} branch exists
run: |
git config --local user.email "actions@github.com"
git config --local user.name "Github Actions"
- name: Install xmllint
run: |
sudo apt-get update
sudo apt-get install -y libxml2-utils
- name: Perform cherry-pick(s)
env: env:
CUT_BRANCH: ${{ needs.setup.outputs.branch }} BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: | run: |
# Function for cherry-picking if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then
cherry_pick () { echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY
local source_branch=$1 exit 1
local destination_branch=$2
# Get project commit/version from source branch
git switch $source_branch
SOURCE_COMMIT=$(git log --reverse --pretty=format:"%H" --max-count=1 Directory.Build.props)
SOURCE_VERSION=$(xmllint -xpath "/Project/PropertyGroup/Version/text()" Directory.Build.props)
# Get project commit/version from destination branch
git switch $destination_branch
DESTINATION_VERSION=$(xmllint -xpath "/Project/PropertyGroup/Version/text()" Directory.Build.props)
if [[ "$DESTINATION_VERSION" != "$SOURCE_VERSION" ]]; then
git cherry-pick --strategy-option=theirs -x $SOURCE_COMMIT
git push -u origin $destination_branch
fi
}
# If we are cutting 'hotfix-rc':
if [[ "$CUT_BRANCH" == "hotfix-rc" ]]; then
# If the 'rc' branch exists:
if [[ $(git ls-remote --heads origin rc) ]]; then
# Chery-pick from 'rc' into 'hotfix-rc'
cherry_pick rc hotfix-rc
# Cherry-pick from 'main' into 'rc'
cherry_pick main rc
# If the 'rc' branch does not exist:
else
# Cherry-pick from 'main' into 'hotfix-rc'
cherry_pick main hotfix-rc
fi
# If we are cutting 'rc':
elif [[ "$CUT_BRANCH" == "rc" ]]; then
# Cherry-pick from 'main' into 'rc'
cherry_pick main rc
fi fi
- name: Cut branch
env:
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
git switch --quiet --create $BRANCH_NAME
git push --quiet --set-upstream origin $BRANCH_NAME
move_future_db_scripts: move_future_db_scripts:
name: Move finalization database scripts name: Move finalization database scripts
needs: cherry_pick needs: cut_branch
uses: ./.github/workflows/_move_finalization_db_scripts.yml uses: ./.github/workflows/_move_finalization_db_scripts.yml
secrets: inherit secrets: inherit

View File

@ -8,6 +8,11 @@ jobs:
stale: stale:
name: Check for stale issues and PRs name: Check for stale issues and PRs
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions:
actions: write
contents: read
issues: write
pull-requests: write
steps: steps:
- name: Check - name: Check
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0

View File

@ -31,10 +31,17 @@ on:
- "test/Infrastructure.IntegrationTest/**" # Any changes to the tests - "test/Infrastructure.IntegrationTest/**" # Any changes to the tests
- "src/**/Entities/**/*.cs" # Database entity definitions - "src/**/Entities/**/*.cs" # Database entity definitions
permissions:
contents: read
jobs: jobs:
test: test:
name: Run tests name: Run tests
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions:
contents: read
actions: read
checks: write
steps: steps:
- name: Check out repo - name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

View File

@ -20,6 +20,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService
private readonly Lazy<Task<IChannel>> _lazyChannel; private readonly Lazy<Task<IChannel>> _lazyChannel;
private readonly IRabbitMqService _rabbitMqService; private readonly IRabbitMqService _rabbitMqService;
private readonly ILogger<RabbitMqIntegrationListenerService> _logger; private readonly ILogger<RabbitMqIntegrationListenerService> _logger;
private readonly TimeProvider _timeProvider;
public RabbitMqIntegrationListenerService(IIntegrationHandler handler, public RabbitMqIntegrationListenerService(IIntegrationHandler handler,
string routingKey, string routingKey,
@ -27,7 +28,8 @@ public class RabbitMqIntegrationListenerService : BackgroundService
string retryQueueName, string retryQueueName,
int maxRetries, int maxRetries,
IRabbitMqService rabbitMqService, IRabbitMqService rabbitMqService,
ILogger<RabbitMqIntegrationListenerService> logger) ILogger<RabbitMqIntegrationListenerService> logger,
TimeProvider timeProvider)
{ {
_handler = handler; _handler = handler;
_routingKey = routingKey; _routingKey = routingKey;
@ -35,6 +37,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService
_queueName = queueName; _queueName = queueName;
_rabbitMqService = rabbitMqService; _rabbitMqService = rabbitMqService;
_logger = logger; _logger = logger;
_timeProvider = timeProvider;
_maxRetries = maxRetries; _maxRetries = maxRetries;
_lazyChannel = new Lazy<Task<IChannel>>(() => _rabbitMqService.CreateChannelAsync()); _lazyChannel = new Lazy<Task<IChannel>>(() => _rabbitMqService.CreateChannelAsync());
} }
@ -74,7 +77,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService
var integrationMessage = JsonSerializer.Deserialize<IntegrationMessage>(json); var integrationMessage = JsonSerializer.Deserialize<IntegrationMessage>(json);
if (integrationMessage is not null && if (integrationMessage is not null &&
integrationMessage.DelayUntilDate.HasValue && integrationMessage.DelayUntilDate.HasValue &&
integrationMessage.DelayUntilDate.Value > DateTime.UtcNow) integrationMessage.DelayUntilDate.Value > _timeProvider.GetUtcNow().UtcDateTime)
{ {
await _rabbitMqService.RepublishToRetryQueueAsync(channel, ea); await _rabbitMqService.RepublishToRetryQueueAsync(channel, ea);
await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken); await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);

View File

@ -9,7 +9,9 @@ using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Services; namespace Bit.Core.Services;
public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory) public class WebhookIntegrationHandler(
IHttpClientFactory httpClientFactory,
TimeProvider timeProvider)
: IntegrationHandlerBase<WebhookIntegrationConfigurationDetails> : IntegrationHandlerBase<WebhookIntegrationConfigurationDetails>
{ {
private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName); private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName);
@ -39,7 +41,7 @@ public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory)
if (int.TryParse(value, out var seconds)) if (int.TryParse(value, out var seconds))
{ {
// Retry-after was specified in seconds. Adjust DelayUntilDate by the requested number of seconds. // Retry-after was specified in seconds. Adjust DelayUntilDate by the requested number of seconds.
result.DelayUntilDate = DateTime.UtcNow.AddSeconds(seconds); result.DelayUntilDate = timeProvider.GetUtcNow().AddSeconds(seconds).UtcDateTime;
} }
else if (DateTimeOffset.TryParseExact(value, else if (DateTimeOffset.TryParseExact(value,
"r", // "r" is the round-trip format: RFC1123 "r", // "r" is the round-trip format: RFC1123

View File

@ -138,6 +138,7 @@ public static class FeatureFlagKeys
public const string EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill"; public const string EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill";
public const string MacOsNativeCredentialSync = "macos-native-credential-sync"; public const string MacOsNativeCredentialSync = "macos-native-credential-sync";
public const string InlineMenuTotp = "inline-menu-totp"; public const string InlineMenuTotp = "inline-menu-totp";
public const string WindowsDesktopAutotype = "windows-desktop-autotype";
/* Billing Team */ /* Billing Team */
public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email"; public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email";
@ -206,6 +207,7 @@ public static class FeatureFlagKeys
public const string EndUserNotifications = "pm-10609-end-user-notifications"; public const string EndUserNotifications = "pm-10609-end-user-notifications";
public const string PhishingDetection = "phishing-detection"; public const string PhishingDetection = "phishing-detection";
public const string RemoveCardItemTypePolicy = "pm-16442-remove-card-item-type-policy"; public const string RemoveCardItemTypePolicy = "pm-16442-remove-card-item-type-policy";
public const string PM22134SdkCipherListView = "pm-22134-sdk-cipher-list-view";
public static List<string> GetAllKeys() public static List<string> GetAllKeys()
{ {

View File

@ -717,7 +717,8 @@ public static class ServiceCollectionExtensions
retryQueueName: integrationRetryQueueName, retryQueueName: integrationRetryQueueName,
maxRetries: maxRetries, maxRetries: maxRetries,
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(), rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
logger: provider.GetRequiredService<ILogger<RabbitMqIntegrationListenerService>>())); logger: provider.GetRequiredService<ILogger<RabbitMqIntegrationListenerService>>(),
timeProvider: provider.GetRequiredService<TimeProvider>()));
return services; return services;
} }

View File

@ -52,7 +52,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_FailureNotRetryable_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message) public async Task HandleMessageAsync_FailureNotRetryable_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
{ {
var sutProvider = GetSutProvider(); var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.RetryCount = 0; message.RetryCount = 0;
var result = new IntegrationHandlerResult(false, message); var result = new IntegrationHandlerResult(false, message);
@ -71,7 +70,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_FailureRetryableButTooManyRetries_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message) public async Task HandleMessageAsync_FailureRetryableButTooManyRetries_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
{ {
var sutProvider = GetSutProvider(); var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.RetryCount = _maxRetries; message.RetryCount = _maxRetries;
var result = new IntegrationHandlerResult(false, message); var result = new IntegrationHandlerResult(false, message);
result.Retryable = true; result.Retryable = true;
@ -90,12 +88,10 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_FailureRetryable_PublishesToRetryQueue(IntegrationMessage<WebhookIntegrationConfiguration> message) public async Task HandleMessageAsync_FailureRetryable_PublishesToRetryQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
{ {
var sutProvider = GetSutProvider(); var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
message.RetryCount = 0; message.RetryCount = 0;
var result = new IntegrationHandlerResult(false, message); var result = new IntegrationHandlerResult(false, message);
result.Retryable = true; result.Retryable = true;
result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1);
_handler.HandleAsync(Arg.Any<string>()).Returns(result); _handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = (IntegrationMessage<WebhookIntegrationConfiguration>)IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson())!; var expected = (IntegrationMessage<WebhookIntegrationConfiguration>)IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson())!;
@ -110,7 +106,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
public async Task HandleMessageAsync_SuccessfulResult_Succeeds(IntegrationMessage<WebhookIntegrationConfiguration> message) public async Task HandleMessageAsync_SuccessfulResult_Succeeds(IntegrationMessage<WebhookIntegrationConfiguration> message)
{ {
var sutProvider = GetSutProvider(); var sutProvider = GetSutProvider();
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
var result = new IntegrationHandlerResult(true, message); var result = new IntegrationHandlerResult(true, message);
_handler.HandleAsync(Arg.Any<string>()).Returns(result); _handler.HandleAsync(Arg.Any<string>()).Returns(result);

View File

@ -4,6 +4,7 @@ using Bit.Core.Services;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers; using Bit.Test.Common.Helpers;
using Microsoft.Extensions.Time.Testing;
using NSubstitute; using NSubstitute;
using RabbitMQ.Client; using RabbitMQ.Client;
using RabbitMQ.Client.Events; using RabbitMQ.Client.Events;
@ -18,19 +19,24 @@ public class RabbitMqIntegrationListenerServiceTests
private const string _queueName = "test_queue"; private const string _queueName = "test_queue";
private const string _retryQueueName = "test_queue_retry"; private const string _retryQueueName = "test_queue_retry";
private const string _routingKey = "test_routing_key"; private const string _routingKey = "test_routing_key";
private readonly DateTime _now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>(); private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>();
private readonly IRabbitMqService _rabbitMqService = Substitute.For<IRabbitMqService>(); private readonly IRabbitMqService _rabbitMqService = Substitute.For<IRabbitMqService>();
private SutProvider<RabbitMqIntegrationListenerService> GetSutProvider() private SutProvider<RabbitMqIntegrationListenerService> GetSutProvider()
{ {
return new SutProvider<RabbitMqIntegrationListenerService>() var sutProvider = new SutProvider<RabbitMqIntegrationListenerService>()
.SetDependency(_handler) .SetDependency(_handler)
.SetDependency(_rabbitMqService) .SetDependency(_rabbitMqService)
.SetDependency(_queueName, "queueName") .SetDependency(_queueName, "queueName")
.SetDependency(_retryQueueName, "retryQueueName") .SetDependency(_retryQueueName, "retryQueueName")
.SetDependency(_routingKey, "routingKey") .SetDependency(_routingKey, "routingKey")
.SetDependency(_maxRetries, "maxRetries") .SetDependency(_maxRetries, "maxRetries")
.WithFakeTimeProvider()
.Create(); .Create();
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(_now);
return sutProvider;
} }
[Fact] [Fact]
@ -55,7 +61,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None; var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken); await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); message.DelayUntilDate = null;
message.RetryCount = 0; message.RetryCount = 0;
var eventArgs = new BasicDeliverEventArgs( var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty, consumerTag: string.Empty,
@ -94,7 +100,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None; var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken); await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); message.DelayUntilDate = null;
message.RetryCount = _maxRetries; message.RetryCount = _maxRetries;
var eventArgs = new BasicDeliverEventArgs( var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty, consumerTag: string.Empty,
@ -132,7 +138,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None; var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken); await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); message.DelayUntilDate = null;
message.RetryCount = 0; message.RetryCount = 0;
var eventArgs = new BasicDeliverEventArgs( var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty, consumerTag: string.Empty,
@ -145,7 +151,7 @@ public class RabbitMqIntegrationListenerServiceTests
); );
var result = new IntegrationHandlerResult(false, message); var result = new IntegrationHandlerResult(false, message);
result.Retryable = true; result.Retryable = true;
result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1); result.DelayUntilDate = _now.AddMinutes(1);
_handler.HandleAsync(Arg.Any<string>()).Returns(result); _handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson()); var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
@ -173,7 +179,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None; var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken); await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); message.DelayUntilDate = null;
var eventArgs = new BasicDeliverEventArgs( var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty, consumerTag: string.Empty,
deliveryTag: 0, deliveryTag: 0,
@ -205,7 +211,7 @@ public class RabbitMqIntegrationListenerServiceTests
var cancellationToken = CancellationToken.None; var cancellationToken = CancellationToken.None;
await sutProvider.Sut.StartAsync(cancellationToken); await sutProvider.Sut.StartAsync(cancellationToken);
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(1); message.DelayUntilDate = _now.AddMinutes(1);
var eventArgs = new BasicDeliverEventArgs( var eventArgs = new BasicDeliverEventArgs(
consumerTag: string.Empty, consumerTag: string.Empty,
deliveryTag: 0, deliveryTag: 0,

View File

@ -5,6 +5,7 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers; using Bit.Test.Common.Helpers;
using Bit.Test.Common.MockedHttpClient; using Bit.Test.Common.MockedHttpClient;
using Microsoft.Extensions.Time.Testing;
using NSubstitute; using NSubstitute;
using Xunit; using Xunit;
@ -33,6 +34,7 @@ public class WebhookIntegrationHandlerTests
return new SutProvider<WebhookIntegrationHandler>() return new SutProvider<WebhookIntegrationHandler>()
.SetDependency(clientFactory) .SetDependency(clientFactory)
.WithFakeTimeProvider()
.Create(); .Create();
} }
@ -62,9 +64,13 @@ public class WebhookIntegrationHandlerTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsNotBeforUtc(IntegrationMessage<WebhookIntegrationConfigurationDetails> message) public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
{ {
var sutProvider = GetSutProvider(); var sutProvider = GetSutProvider();
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
var retryAfter = now.AddSeconds(60);
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(now);
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl); message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
_handler.Fallback _handler.Fallback
@ -78,19 +84,21 @@ public class WebhookIntegrationHandlerTests
Assert.True(result.Retryable); Assert.True(result.Retryable);
Assert.Equal(result.Message, message); Assert.Equal(result.Message, message);
Assert.True(result.DelayUntilDate.HasValue); Assert.True(result.DelayUntilDate.HasValue);
Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61)); Assert.Equal(retryAfter, result.DelayUntilDate.Value);
Assert.Equal("Too Many Requests", result.FailureReason); Assert.Equal("Too Many Requests", result.FailureReason);
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsNotBeforUtc(IntegrationMessage<WebhookIntegrationConfigurationDetails> message) public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
{ {
var sutProvider = GetSutProvider(); var sutProvider = GetSutProvider();
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
var retryAfter = now.AddSeconds(60);
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl); message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
_handler.Fallback _handler.Fallback
.WithStatusCode(HttpStatusCode.TooManyRequests) .WithStatusCode(HttpStatusCode.TooManyRequests)
.WithHeader("Retry-After", DateTime.UtcNow.AddSeconds(60).ToString("r")) // "r" is the round-trip format: RFC1123 .WithHeader("Retry-After", retryAfter.ToString("r"))
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>")); .WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
var result = await sutProvider.Sut.HandleAsync(message); var result = await sutProvider.Sut.HandleAsync(message);
@ -99,7 +107,7 @@ public class WebhookIntegrationHandlerTests
Assert.True(result.Retryable); Assert.True(result.Retryable);
Assert.Equal(result.Message, message); Assert.Equal(result.Message, message);
Assert.True(result.DelayUntilDate.HasValue); Assert.True(result.DelayUntilDate.HasValue);
Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61)); Assert.Equal(retryAfter, result.DelayUntilDate.Value);
Assert.Equal("Too Many Requests", result.FailureReason); Assert.Equal("Too Many Requests", result.FailureReason);
} }