275 lines
8.9 KiB
C#
275 lines
8.9 KiB
C#
using Socialize.Api.Modules.Feedback.Data;
|
|
using Socialize.Api.Modules.Feedback.Contracts;
|
|
using Socialize.Api.Modules.Feedback.Services;
|
|
using System.Text.Json;
|
|
|
|
namespace Socialize.Tests.Feedback;
|
|
|
|
public class FeedbackRulesTests
|
|
{
|
|
[Theory]
|
|
[InlineData("Bug", FeedbackType.Bug)]
|
|
[InlineData("suggestion", FeedbackType.Suggestion)]
|
|
[InlineData("Request", FeedbackType.Request)]
|
|
public void TryParseType_accepts_supported_types(string value, FeedbackType expected)
|
|
{
|
|
bool parsed = FeedbackRules.TryParseType(value, out FeedbackType type);
|
|
|
|
Assert.True(parsed);
|
|
Assert.Equal(expected, type);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData("Question")]
|
|
[InlineData("Incident")]
|
|
public void TryParseType_rejects_unsupported_types(string value)
|
|
{
|
|
bool parsed = FeedbackRules.TryParseType(value, out _);
|
|
|
|
Assert.False(parsed);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("New", FeedbackStatus.New)]
|
|
[InlineData("Planned", FeedbackStatus.Planned)]
|
|
[InlineData("Resolved", FeedbackStatus.Resolved)]
|
|
[InlineData("Won't Do", FeedbackStatus.WontDo)]
|
|
[InlineData("WontDo", FeedbackStatus.WontDo)]
|
|
[InlineData("Cancelled", FeedbackStatus.Cancelled)]
|
|
public void TryParseStatus_accepts_supported_statuses(string value, FeedbackStatus expected)
|
|
{
|
|
bool parsed = FeedbackRules.TryParseStatus(value, out FeedbackStatus status);
|
|
|
|
Assert.True(parsed);
|
|
Assert.Equal(expected, status);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanDeveloperSetStatus_rejects_cancelled_destination()
|
|
{
|
|
bool allowed = FeedbackRules.CanDeveloperSetStatus(FeedbackStatus.New, FeedbackStatus.Cancelled);
|
|
|
|
Assert.False(allowed);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanDeveloperSetStatus_rejects_changes_after_cancelled()
|
|
{
|
|
bool allowed = FeedbackRules.CanDeveloperSetStatus(FeedbackStatus.Cancelled, FeedbackStatus.Planned);
|
|
|
|
Assert.False(allowed);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanReporterCancel_rejects_cancelled_report()
|
|
{
|
|
bool allowed = FeedbackRules.CanReporterCancel(FeedbackStatus.Cancelled);
|
|
|
|
Assert.False(allowed);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanReporterAccess_allows_report_owner()
|
|
{
|
|
Guid reporterUserId = Guid.NewGuid();
|
|
FeedbackReport report = new() { ReporterUserId = reporterUserId };
|
|
|
|
bool allowed = FeedbackAccessRules.CanReporterAccess(report, reporterUserId);
|
|
|
|
Assert.True(allowed);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanReporterAccess_rejects_other_users()
|
|
{
|
|
FeedbackReport report = new() { ReporterUserId = Guid.NewGuid() };
|
|
|
|
bool allowed = FeedbackAccessRules.CanReporterAccess(report, Guid.NewGuid());
|
|
|
|
Assert.False(allowed);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanReporterCancel_requires_owner_and_non_final_status()
|
|
{
|
|
Guid reporterUserId = Guid.NewGuid();
|
|
FeedbackReport report = new()
|
|
{
|
|
ReporterUserId = reporterUserId,
|
|
Status = FeedbackStatus.New,
|
|
};
|
|
|
|
bool ownerAllowed = FeedbackAccessRules.CanReporterCancel(report, reporterUserId);
|
|
bool otherUserAllowed = FeedbackAccessRules.CanReporterCancel(report, Guid.NewGuid());
|
|
|
|
Assert.True(ownerAllowed);
|
|
Assert.False(otherUserAllowed);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanReporterComment_requires_report_owner()
|
|
{
|
|
Guid reporterUserId = Guid.NewGuid();
|
|
FeedbackReport report = new() { ReporterUserId = reporterUserId };
|
|
|
|
bool ownerAllowed = FeedbackAccessRules.CanReporterComment(report, reporterUserId);
|
|
bool otherUserAllowed = FeedbackAccessRules.CanReporterComment(report, Guid.NewGuid());
|
|
|
|
Assert.True(ownerAllowed);
|
|
Assert.False(otherUserAllowed);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanDeveloperComment_requires_developer_role()
|
|
{
|
|
Assert.True(FeedbackAccessRules.CanDeveloperComment(isDeveloper: true));
|
|
Assert.False(FeedbackAccessRules.CanDeveloperComment(isDeveloper: false));
|
|
}
|
|
|
|
[Fact]
|
|
public void CanAccessScreenshot_allows_report_owner()
|
|
{
|
|
Guid reporterUserId = Guid.NewGuid();
|
|
FeedbackReport report = new() { ReporterUserId = reporterUserId };
|
|
|
|
bool allowed = FeedbackAccessRules.CanAccessScreenshot(report, reporterUserId, isDeveloper: false);
|
|
|
|
Assert.True(allowed);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanAccessScreenshot_allows_developer()
|
|
{
|
|
FeedbackReport report = new() { ReporterUserId = Guid.NewGuid() };
|
|
|
|
bool allowed = FeedbackAccessRules.CanAccessScreenshot(report, Guid.NewGuid(), isDeveloper: true);
|
|
|
|
Assert.True(allowed);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanAccessScreenshot_rejects_unrelated_non_developer()
|
|
{
|
|
FeedbackReport report = new() { ReporterUserId = Guid.NewGuid() };
|
|
|
|
bool allowed = FeedbackAccessRules.CanAccessScreenshot(report, Guid.NewGuid(), isDeveloper: false);
|
|
|
|
Assert.False(allowed);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("image/png")]
|
|
[InlineData("image/jpeg")]
|
|
[InlineData("image/jpg")]
|
|
public void Screenshot_content_type_allows_png_and_jpeg(string contentType)
|
|
{
|
|
bool allowed = FeedbackScreenshotRules.IsAllowedContentType(contentType);
|
|
|
|
Assert.True(allowed);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("text/html")]
|
|
[InlineData("application/pdf")]
|
|
[InlineData("")]
|
|
public void Screenshot_content_type_rejects_non_images(string contentType)
|
|
{
|
|
bool allowed = FeedbackScreenshotRules.IsAllowedContentType(contentType);
|
|
|
|
Assert.False(allowed);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(FeedbackScreenshotRules.MaxScreenshotBytes)]
|
|
public void Screenshot_size_allows_non_empty_files_up_to_limit(long sizeBytes)
|
|
{
|
|
bool allowed = FeedbackScreenshotRules.IsAllowedSize(sizeBytes);
|
|
|
|
Assert.True(allowed);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(FeedbackScreenshotRules.MaxScreenshotBytes + 1)]
|
|
public void Screenshot_size_rejects_empty_and_oversized_files(long sizeBytes)
|
|
{
|
|
bool allowed = FeedbackScreenshotRules.IsAllowedSize(sizeBytes);
|
|
|
|
Assert.False(allowed);
|
|
}
|
|
|
|
[Fact]
|
|
public void NormalizeTags_trims_deduplicates_and_orders()
|
|
{
|
|
IReadOnlyCollection<string> tags = FeedbackRules.NormalizeTags([" mobile ", "bug", "Mobile", ""]);
|
|
|
|
Assert.Equal(["bug", "mobile"], tags);
|
|
}
|
|
|
|
[Fact]
|
|
public void Feedback_report_dto_returns_mixed_timeline_in_created_order()
|
|
{
|
|
Guid reportId = Guid.NewGuid();
|
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
|
FeedbackReport report = new()
|
|
{
|
|
Id = reportId,
|
|
Type = FeedbackType.Bug,
|
|
Status = FeedbackStatus.New,
|
|
Description = "Broken layout",
|
|
ReporterUserId = Guid.NewGuid(),
|
|
ReporterDisplayName = "Reporter",
|
|
ReporterEmail = "reporter@example.com",
|
|
SubmittedPath = "/app/example",
|
|
CreatedAt = now,
|
|
LastActivityAt = now.AddMinutes(2),
|
|
};
|
|
report.ActivityEntries.Add(new FeedbackActivityEntry
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
FeedbackReportId = reportId,
|
|
ActorUserId = Guid.NewGuid(),
|
|
ActorDisplayName = "Developer",
|
|
ActorEmail = "dev@example.com",
|
|
ActivityType = FeedbackActivityTypes.StatusChanged,
|
|
FromValue = "New",
|
|
ToValue = "Planned",
|
|
CreatedAt = now.AddMinutes(2),
|
|
});
|
|
report.Comments.Add(new FeedbackComment
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
FeedbackReportId = reportId,
|
|
AuthorUserId = report.ReporterUserId,
|
|
AuthorDisplayName = "Reporter",
|
|
AuthorEmail = "reporter@example.com",
|
|
AuthorRole = "Reporter",
|
|
Body = "More context",
|
|
CreatedAt = now.AddMinutes(1),
|
|
});
|
|
|
|
FeedbackReportDto dto = report.ToDto();
|
|
|
|
Assert.Equal(["Comment", "Activity"], dto.Timeline.Select(item => item.Kind).ToArray());
|
|
Assert.Equal("More context", dto.Timeline.First().Body);
|
|
Assert.Equal(FeedbackActivityTypes.StatusChanged, dto.Timeline.Last().ActivityType);
|
|
}
|
|
|
|
[Fact]
|
|
public void Feedback_notification_metadata_includes_target_route()
|
|
{
|
|
Guid reportId = Guid.NewGuid();
|
|
|
|
string developerMetadata = FeedbackNotificationRoutes.BuildMetadataJson(reportId, developerRoute: true);
|
|
string reporterMetadata = FeedbackNotificationRoutes.BuildMetadataJson(reportId, developerRoute: false);
|
|
|
|
using JsonDocument developerDocument = JsonDocument.Parse(developerMetadata);
|
|
using JsonDocument reporterDocument = JsonDocument.Parse(reporterMetadata);
|
|
Assert.Equal($"/app/feedback/{reportId}", developerDocument.RootElement.GetProperty("route").GetString());
|
|
Assert.Equal($"/app/my-feedback/{reportId}", reporterDocument.RootElement.GetProperty("route").GetString());
|
|
Assert.True(developerDocument.RootElement.GetProperty("isFeedbackNotification").GetBoolean());
|
|
}
|
|
}
|