193 lines
7.1 KiB
C#
193 lines
7.1 KiB
C#
using Socialize.Api.Modules.ReleaseCommunications.Contracts;
|
|
using Socialize.Api.Modules.ReleaseCommunications.Data;
|
|
using Socialize.Api.Modules.ReleaseCommunications.Services;
|
|
|
|
namespace Socialize.Tests.ReleaseCommunications;
|
|
|
|
public class ReleaseUpdateRulesTests
|
|
{
|
|
[Theory]
|
|
[InlineData("Feature", ReleaseUpdateCategory.Feature)]
|
|
[InlineData("improvement", ReleaseUpdateCategory.Improvement)]
|
|
[InlineData("Breaking Change", ReleaseUpdateCategory.BreakingChange)]
|
|
[InlineData("BreakingChange", ReleaseUpdateCategory.BreakingChange)]
|
|
internal void TryParseCategory_accepts_supported_categories(string value, ReleaseUpdateCategory expected)
|
|
{
|
|
bool parsed = ReleaseUpdateRules.TryParseCategory(value, out ReleaseUpdateCategory category);
|
|
|
|
Assert.True(parsed);
|
|
Assert.Equal(expected, category);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData("Security")]
|
|
[InlineData("Maintenance")]
|
|
public void TryParseCategory_rejects_unsupported_categories(string value)
|
|
{
|
|
bool parsed = ReleaseUpdateRules.TryParseCategory(value, out _);
|
|
|
|
Assert.False(parsed);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Normal", ReleaseUpdateImportance.Normal)]
|
|
[InlineData("important", ReleaseUpdateImportance.Important)]
|
|
internal void TryParseImportance_accepts_supported_importance(string value, ReleaseUpdateImportance expected)
|
|
{
|
|
bool parsed = ReleaseUpdateRules.TryParseImportance(value, out ReleaseUpdateImportance importance);
|
|
|
|
Assert.True(parsed);
|
|
Assert.Equal(expected, importance);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Everyone", ReleaseUpdateAudience.Everyone)]
|
|
[InlineData("Organization Owners", ReleaseUpdateAudience.OrganizationOwners)]
|
|
[InlineData("developers", ReleaseUpdateAudience.Developers)]
|
|
internal void TryParseAudience_accepts_supported_audiences(string value, ReleaseUpdateAudience expected)
|
|
{
|
|
bool parsed = ReleaseUpdateRules.TryParseAudience(value, out ReleaseUpdateAudience audience);
|
|
|
|
Assert.True(parsed);
|
|
Assert.Equal(expected, audience);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToDto_formats_breaking_change_category_for_display()
|
|
{
|
|
ReleaseUpdate update = new()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Title = "API change",
|
|
Summary = "A workflow API changed.",
|
|
Category = ReleaseUpdateCategory.BreakingChange,
|
|
Importance = ReleaseUpdateImportance.Important,
|
|
Audience = ReleaseUpdateAudience.Developers,
|
|
Status = ReleaseUpdateStatus.Published,
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
|
UpdatedAt = DateTimeOffset.UtcNow,
|
|
PublishedAt = DateTimeOffset.UtcNow,
|
|
CreatedByUserId = Guid.NewGuid(),
|
|
};
|
|
|
|
ReleaseUpdateDto dto = update.ToDto(isRead: true);
|
|
|
|
Assert.Equal("Breaking Change", dto.Category);
|
|
Assert.True(dto.IsRead);
|
|
}
|
|
|
|
[Fact]
|
|
public void VisibleTo_returns_everyone_updates_for_any_authenticated_user()
|
|
{
|
|
ReleaseUpdate update = NewPublishedUpdate(ReleaseUpdateAudience.Everyone);
|
|
|
|
List<ReleaseUpdate> visibleUpdates = new[] { update }
|
|
.AsQueryable()
|
|
.VisibleTo(new ReleaseUpdateAudienceContext(IsDeveloper: false, IsOrganizationOwner: false))
|
|
.ToList();
|
|
|
|
Assert.Same(update, Assert.Single(visibleUpdates));
|
|
}
|
|
|
|
[Fact]
|
|
public void VisibleTo_rejects_unpublished_updates()
|
|
{
|
|
ReleaseUpdate update = NewPublishedUpdate(ReleaseUpdateAudience.Everyone);
|
|
update.Status = ReleaseUpdateStatus.Draft;
|
|
|
|
List<ReleaseUpdate> visibleUpdates = new[] { update }
|
|
.AsQueryable()
|
|
.VisibleTo(new ReleaseUpdateAudienceContext(IsDeveloper: true, IsOrganizationOwner: true))
|
|
.ToList();
|
|
|
|
Assert.Empty(visibleUpdates);
|
|
}
|
|
|
|
[Fact]
|
|
public void VisibleTo_requires_matching_restricted_audience()
|
|
{
|
|
ReleaseUpdate ownerUpdate = NewPublishedUpdate(ReleaseUpdateAudience.OrganizationOwners);
|
|
ReleaseUpdate developerUpdate = NewPublishedUpdate(ReleaseUpdateAudience.Developers);
|
|
|
|
List<ReleaseUpdate> ownerVisibleUpdates = new[] { ownerUpdate, developerUpdate }
|
|
.AsQueryable()
|
|
.VisibleTo(new ReleaseUpdateAudienceContext(IsDeveloper: false, IsOrganizationOwner: true))
|
|
.ToList();
|
|
|
|
List<ReleaseUpdate> developerVisibleUpdates = new[] { ownerUpdate, developerUpdate }
|
|
.AsQueryable()
|
|
.VisibleTo(new ReleaseUpdateAudienceContext(IsDeveloper: true, IsOrganizationOwner: false))
|
|
.ToList();
|
|
|
|
Assert.Same(ownerUpdate, Assert.Single(ownerVisibleUpdates));
|
|
Assert.Same(developerUpdate, Assert.Single(developerVisibleUpdates));
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateMissingReadReceipts_creates_receipts_only_for_unread_visible_updates()
|
|
{
|
|
Guid userId = Guid.NewGuid();
|
|
Guid unreadUpdateId = Guid.NewGuid();
|
|
Guid readUpdateId = Guid.NewGuid();
|
|
DateTimeOffset readAt = DateTimeOffset.UtcNow;
|
|
|
|
IReadOnlyCollection<ReleaseUpdateReadReceipt> receipts =
|
|
ReleaseUpdateReadState.CreateMissingReadReceipts(
|
|
userId,
|
|
[unreadUpdateId, readUpdateId],
|
|
new HashSet<Guid> { readUpdateId },
|
|
readAt);
|
|
|
|
ReleaseUpdateReadReceipt receipt = Assert.Single(receipts);
|
|
Assert.Equal(unreadUpdateId, receipt.ReleaseUpdateId);
|
|
Assert.Equal(userId, receipt.UserId);
|
|
Assert.Equal(readAt, receipt.ReadAt);
|
|
}
|
|
|
|
[Fact]
|
|
public void IsInactive_allows_never_authenticated_and_old_activity()
|
|
{
|
|
DateTimeOffset inactiveBefore = DateTimeOffset.UtcNow.AddHours(-24);
|
|
|
|
Assert.True(ReleaseUpdateEmailRules.IsInactive(null, inactiveBefore));
|
|
Assert.True(ReleaseUpdateEmailRules.IsInactive(inactiveBefore.AddMinutes(-1), inactiveBefore));
|
|
}
|
|
|
|
[Fact]
|
|
public void IsInactive_rejects_recent_activity()
|
|
{
|
|
DateTimeOffset inactiveBefore = DateTimeOffset.UtcNow.AddHours(-24);
|
|
|
|
Assert.False(ReleaseUpdateEmailRules.IsInactive(inactiveBefore.AddMinutes(1), inactiveBefore));
|
|
}
|
|
|
|
[Fact]
|
|
public void CanSendDigest_enforces_send_interval()
|
|
{
|
|
DateTimeOffset lastSentBefore = DateTimeOffset.UtcNow.AddHours(-24);
|
|
|
|
Assert.True(ReleaseUpdateEmailRules.CanSendDigest(null, lastSentBefore));
|
|
Assert.True(ReleaseUpdateEmailRules.CanSendDigest(lastSentBefore.AddMinutes(-1), lastSentBefore));
|
|
Assert.False(ReleaseUpdateEmailRules.CanSendDigest(lastSentBefore.AddMinutes(1), lastSentBefore));
|
|
}
|
|
|
|
private static ReleaseUpdate NewPublishedUpdate(ReleaseUpdateAudience audience)
|
|
{
|
|
return new ReleaseUpdate
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Title = "Update",
|
|
Summary = "Something changed.",
|
|
Category = ReleaseUpdateCategory.Improvement,
|
|
Importance = ReleaseUpdateImportance.Normal,
|
|
Audience = audience,
|
|
Status = ReleaseUpdateStatus.Published,
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
|
UpdatedAt = DateTimeOffset.UtcNow,
|
|
PublishedAt = DateTimeOffset.UtcNow,
|
|
CreatedByUserId = Guid.NewGuid(),
|
|
};
|
|
}
|
|
}
|