Auto-flag questions with media at write time, not just via one-time sweep
The flag-media-questions CLI command only caught questions that existed in the DB when it ran once. Any question created, imported, or edited afterward could add media without ever being flagged for review, since nothing gated new writes going forward. CreateAsync, UpdateAsync, and UpsertManyByExternalIdAsync now all run new/updated questions through ApplyMediaReviewGate, which flags any question that has media and isn't already flagged.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
using QuestionsApi.Models;
|
||||
using QuestionsApi.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace QuestionsApi.Tests;
|
||||
|
||||
public sealed class QuestionStoreTests
|
||||
{
|
||||
[Fact]
|
||||
public void ApplyMediaReviewGate_FlagsUnflaggedQuestionWithMedia()
|
||||
{
|
||||
var question = new Question { Media = ["asset-1"] };
|
||||
|
||||
QuestionStore.ApplyMediaReviewGate(question);
|
||||
|
||||
Assert.NotNull(question.FlaggedForReviewUtc);
|
||||
Assert.Equal("system", question.FlaggedByUserId);
|
||||
Assert.False(string.IsNullOrWhiteSpace(question.FlagReason));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyMediaReviewGate_LeavesMediaFreeQuestionsAlone()
|
||||
{
|
||||
var question = new Question { Media = [] };
|
||||
|
||||
QuestionStore.ApplyMediaReviewGate(question);
|
||||
|
||||
Assert.Null(question.FlaggedForReviewUtc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyMediaReviewGate_DoesNotOverwriteAnExistingFlag()
|
||||
{
|
||||
var flaggedAt = DateTime.UtcNow.AddDays(-1);
|
||||
var question = new Question
|
||||
{
|
||||
Media = ["asset-1"],
|
||||
FlaggedForReviewUtc = flaggedAt,
|
||||
FlagReason = "Reported by student",
|
||||
FlaggedByUserId = "user-42"
|
||||
};
|
||||
|
||||
QuestionStore.ApplyMediaReviewGate(question);
|
||||
|
||||
Assert.Equal(flaggedAt, question.FlaggedForReviewUtc);
|
||||
Assert.Equal("Reported by student", question.FlagReason);
|
||||
Assert.Equal("user-42", question.FlaggedByUserId);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,9 @@ namespace QuestionsApi.Services;
|
||||
|
||||
public class QuestionStore
|
||||
{
|
||||
private const string MediaReviewReason = "Contains media - pending review";
|
||||
private const string MediaReviewFlaggedBy = "system";
|
||||
|
||||
private readonly IMongoCollection<Question> _col;
|
||||
|
||||
public QuestionStore(MongoDatabaseProvider mongo)
|
||||
@@ -105,6 +108,7 @@ public class QuestionStore
|
||||
var question = FromRequest(req);
|
||||
question.CreatedUtc = DateTime.UtcNow;
|
||||
question.UpdatedUtc = DateTime.UtcNow;
|
||||
ApplyMediaReviewGate(question);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -131,6 +135,7 @@ public class QuestionStore
|
||||
question.FlaggedForReviewUtc = existing.FlaggedForReviewUtc;
|
||||
question.FlagReason = existing.FlagReason;
|
||||
question.FlaggedByUserId = existing.FlaggedByUserId;
|
||||
ApplyMediaReviewGate(question);
|
||||
var result = await _col.ReplaceOneAsync(q => q.Id == id, question);
|
||||
return result.MatchedCount == 0 ? null : question;
|
||||
}
|
||||
@@ -158,6 +163,7 @@ public class QuestionStore
|
||||
question.Id = ObjectId.GenerateNewId().ToString();
|
||||
question.CreatedUtc = DateTime.UtcNow;
|
||||
}
|
||||
ApplyMediaReviewGate(question);
|
||||
|
||||
await _col.ReplaceOneAsync(filter, question, new ReplaceOptions { IsUpsert = true });
|
||||
count++;
|
||||
@@ -246,6 +252,18 @@ public class QuestionStore
|
||||
return (int)result.MatchedCount;
|
||||
}
|
||||
|
||||
// Questions with media must be reviewed before reaching students - gate any question that
|
||||
// gains media and isn't already flagged, so newly created/imported/edited questions can't
|
||||
// slip past the media-review requirement the way a one-time sweep alone would miss.
|
||||
internal static void ApplyMediaReviewGate(Question question)
|
||||
{
|
||||
if (question.Media.Count == 0 || question.FlaggedForReviewUtc is not null)
|
||||
return;
|
||||
question.FlaggedForReviewUtc = DateTime.UtcNow;
|
||||
question.FlagReason = MediaReviewReason;
|
||||
question.FlaggedByUserId = MediaReviewFlaggedBy;
|
||||
}
|
||||
|
||||
private static Question FromRequest(UpsertQuestionRequest req) => new()
|
||||
{
|
||||
ExternalId = req.ExternalId.Trim(),
|
||||
|
||||
Reference in New Issue
Block a user