From 291319e3d3625fc97a2cb316e889f43d0b2bd34b Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 13 Feb 2026 16:15:53 -0500 Subject: [PATCH 1/2] Allow getting a word from the Frontier --- .../Controllers/AudioControllerTests.cs | 4 ++-- .../Controllers/WordControllerTests.cs | 12 +++++----- Backend.Tests/Mocks/WordRepositoryMock.cs | 23 ++++++++++++------- Backend.Tests/Services/MergeServiceTests.cs | 18 +++++++-------- Backend.Tests/Services/WordServiceTests.cs | 10 ++++---- Backend/Controllers/AudioController.cs | 2 +- Backend/Controllers/WordController.cs | 6 ++--- Backend/Interfaces/IWordRepository.cs | 3 ++- Backend/Repositories/WordRepository.cs | 14 ++++++++++- Backend/Services/LiftService.cs | 2 +- Backend/Services/MergeService.cs | 8 +++---- Backend/Services/StatisticsService.cs | 8 +++---- 12 files changed, 65 insertions(+), 45 deletions(-) diff --git a/Backend.Tests/Controllers/AudioControllerTests.cs b/Backend.Tests/Controllers/AudioControllerTests.cs index 2ceb7f9087..a831e82ef1 100644 --- a/Backend.Tests/Controllers/AudioControllerTests.cs +++ b/Backend.Tests/Controllers/AudioControllerTests.cs @@ -182,13 +182,13 @@ public void TestDeleteAudioFile() Assert.That(_wordRepo.GetAllWords(_projId).Result, Has.Count.EqualTo(2)); // Get the new word from the database - var frontier = _wordRepo.GetFrontier(_projId).Result; + var frontier = _wordRepo.GetAllFrontier(_projId).Result; // Ensure the new word has no audio files Assert.That(frontier[0].Audio, Has.Count.EqualTo(0)); // Test the frontier - Assert.That(_wordRepo.GetFrontier(_projId).Result, Has.Count.EqualTo(1)); + Assert.That(_wordRepo.GetAllFrontier(_projId).Result, Has.Count.EqualTo(1)); // Ensure the word with deleted audio is in the frontier Assert.That(frontier, Has.Count.EqualTo(1)); diff --git a/Backend.Tests/Controllers/WordControllerTests.cs b/Backend.Tests/Controllers/WordControllerTests.cs index 5fd64fb7a7..d9fda8eab8 100644 --- a/Backend.Tests/Controllers/WordControllerTests.cs +++ b/Backend.Tests/Controllers/WordControllerTests.cs @@ -71,7 +71,7 @@ public async Task TestDeleteFrontierWord() w.Id == wordToDelete.Id || w.Id == otherWord.Id || w.Accessibility == Status.Deleted)); - var updatedFrontier = await _wordRepo.GetFrontier(ProjId); + var updatedFrontier = await _wordRepo.GetAllFrontier(ProjId); Assert.That(updatedFrontier, Has.Count.EqualTo(1)); Assert.That(updatedFrontier.First().Id, Is.EqualTo(otherWord.Id)); } @@ -248,7 +248,7 @@ public async Task TestRevertWords() }); var reverted = (Dictionary)((OkObjectResult)result).Value!; Assert.That(reverted, Has.Count.EqualTo(1)); - var frontierIds = (await _wordRepo.GetFrontier(ProjId)).Select(w => w.Id).ToList(); + var frontierIds = (await _wordRepo.GetAllFrontier(ProjId)).Select(w => w.Id).ToList(); Assert.That(frontierIds, Has.Count.EqualTo(2)); Assert.That(frontierIds, Does.Contain(frontierWord1.Id)); Assert.That(frontierIds, Does.Contain(reverted[frontierWord0.Id])); @@ -317,7 +317,7 @@ public async Task TestCreateWord() var allWords = await _wordRepo.GetAllWords(ProjId); Assert.That(allWords[0], Is.EqualTo(word).UsingPropertiesComparer()); - var frontier = await _wordRepo.GetFrontier(ProjId); + var frontier = await _wordRepo.GetAllFrontier(ProjId); Assert.That(frontier[0], Is.EqualTo(word).UsingPropertiesComparer()); } @@ -350,7 +350,7 @@ public async Task TestUpdateWord() Assert.That(allWords, Does.Contain(origWord).UsingPropertiesComparer()); Assert.That(allWords, Does.Contain(finalWord).UsingPropertiesComparer()); - var frontier = await _wordRepo.GetFrontier(ProjId); + var frontier = await _wordRepo.GetAllFrontier(ProjId); Assert.That(frontier, Has.Count.EqualTo(1)); Assert.That(frontier, Does.Contain(finalWord).UsingPropertiesComparer()); } @@ -381,14 +381,14 @@ public async Task TestRestoreWord() await _wordRepo.DeleteFrontier(ProjId, word.Id); Assert.That(await _wordRepo.GetAllWords(ProjId), Does.Contain(word).UsingPropertiesComparer()); - Assert.That(await _wordRepo.GetFrontier(ProjId), Is.Empty); + Assert.That(await _wordRepo.GetAllFrontier(ProjId), Is.Empty); var result = await _wordController.RestoreWord(ProjId, word.Id); Assert.That(result, Is.InstanceOf()); Assert.That(((OkObjectResult)result).Value, Is.True); Assert.That(await _wordRepo.GetAllWords(ProjId), Does.Contain(word).UsingPropertiesComparer()); - Assert.That(await _wordRepo.GetFrontier(ProjId), Does.Contain(word).UsingPropertiesComparer()); + Assert.That(await _wordRepo.GetAllFrontier(ProjId), Does.Contain(word).UsingPropertiesComparer()); } [Test] diff --git a/Backend.Tests/Mocks/WordRepositoryMock.cs b/Backend.Tests/Mocks/WordRepositoryMock.cs index d05a6f7e7d..fe0124e5d3 100644 --- a/Backend.Tests/Mocks/WordRepositoryMock.cs +++ b/Backend.Tests/Mocks/WordRepositoryMock.cs @@ -13,8 +13,8 @@ internal sealed class WordRepositoryMock : IWordRepository private readonly List _words = []; private readonly List _frontier = []; - private Task? _getFrontierDelay; - private int _getFrontierCallCount; + private Task? _getAllFrontierDelay; + private int _getAllFrontierCallCount; /// /// Sets a delay for the GetFrontier method. The first call to GetFrontier will wait @@ -22,8 +22,8 @@ internal sealed class WordRepositoryMock : IWordRepository /// public void SetGetFrontierDelay(Task delay) { - _getFrontierDelay = delay; - _getFrontierCallCount = 0; + _getAllFrontierDelay = delay; + _getAllFrontierCallCount = 0; } public Task> GetAllWords(string projectId) @@ -99,21 +99,28 @@ public Task GetFrontierCount(string projectId) return Task.FromResult(_frontier.Count(w => w.ProjectId == projectId)); } - public async Task> GetFrontier(string projectId) + public async Task> GetAllFrontier(string projectId) { - if (_getFrontierDelay is not null) + if (_getAllFrontierDelay is not null) { - var callCount = Interlocked.Increment(ref _getFrontierCallCount); + var callCount = Interlocked.Increment(ref _getAllFrontierCallCount); if (callCount == 1) { // First call waits for the signal - await _getFrontierDelay; + await _getAllFrontierDelay; } } return _frontier.Where(w => w.ProjectId == projectId).Select(w => w.Clone()).ToList(); } + public Task GetFrontier(string projectId, string wordId, string? audioFileName = null) + { + var word = _frontier.Find(w => w.ProjectId == projectId && w.Id == wordId && + (string.IsNullOrEmpty(audioFileName) || w.Audio.Any(a => a.FileName == audioFileName))); + return Task.FromResult(word?.Clone()); + } + public Task> GetFrontierWithVernacular(string projectId, string vernacular) { return Task.FromResult(_frontier.Where( diff --git a/Backend.Tests/Services/MergeServiceTests.cs b/Backend.Tests/Services/MergeServiceTests.cs index 34bfbf55ab..8b47d351f0 100644 --- a/Backend.Tests/Services/MergeServiceTests.cs +++ b/Backend.Tests/Services/MergeServiceTests.cs @@ -55,7 +55,7 @@ public void MergeWordsOneChildTest() Util.AssertEqualWordContent(newWords.First(), thisWord, true); // Check that the only word in the frontier is the new word - var frontier = _wordRepo.GetFrontier(ProjId).Result; + var frontier = _wordRepo.GetAllFrontier(ProjId).Result; Assert.That(frontier, Has.Count.EqualTo(1)); Assert.That(frontier.First(), Is.EqualTo(newWords.First()).UsingPropertiesComparer()); @@ -81,7 +81,7 @@ public void MergeWordsDeleteTest() var newWords = _mergeService.Merge(ProjId, UserId, [mergeObject]).Result; // There should be no word added and no words left in the frontier. Assert.That(newWords, Is.Empty); - var frontier = _wordRepo.GetFrontier(ProjId).Result; + var frontier = _wordRepo.GetAllFrontier(ProjId).Result; Assert.That(frontier, Is.Empty); } @@ -105,7 +105,7 @@ public void MergeWordsMultiChildTest() Assert.That(_wordRepo.GetWord(ProjId, id).Result, Is.Not.Null); mergeWords.Children.Add(new MergeSourceWord { SrcWordId = id }); } - Assert.That(_wordRepo.GetFrontier(ProjId).Result, Has.Count.EqualTo(numberOfChildren)); + Assert.That(_wordRepo.GetAllFrontier(ProjId).Result, Has.Count.EqualTo(numberOfChildren)); var mergeWordsList = new List { mergeWords }; var newWords = _mergeService.Merge(ProjId, UserId, mergeWordsList).Result; @@ -120,7 +120,7 @@ public void MergeWordsMultiChildTest() // Confirm that parent added to repo and children not in frontier. Assert.That(_wordRepo.GetWord(ProjId, dbParent.Id).Result, Is.Not.Null); - Assert.That(_wordRepo.GetFrontier(ProjId).Result, Has.Count.EqualTo(1)); + Assert.That(_wordRepo.GetAllFrontier(ProjId).Result, Has.Count.EqualTo(1)); } [Test] @@ -134,7 +134,7 @@ public void MergeWordsMultipleTest() Assert.That(newWords, Has.Count.EqualTo(wordCount)); Assert.That(newWords.First().Id, Is.Not.EqualTo(newWords.Last().Id)); - var frontier = _wordRepo.GetFrontier(ProjId).Result; + var frontier = _wordRepo.GetAllFrontier(ProjId).Result; Assert.That(frontier, Has.Count.EqualTo(wordCount)); Assert.That(frontier.First().Id, Is.Not.EqualTo(frontier.Last().Id)); Assert.That(newWords, Does.Contain(frontier.First()).UsingPropertiesComparer()); @@ -165,7 +165,7 @@ public void UndoMergeOneChildTest() var undo = _mergeService.UndoMerge(ProjId, UserId, mergedWord).Result; Assert.That(undo, Is.True); - var frontierWords = _wordRepo.GetFrontier(ProjId).Result; + var frontierWords = _wordRepo.GetAllFrontier(ProjId).Result; var frontierWordIds = frontierWords.Select(word => word.Id).ToList(); Assert.That(frontierWords, Has.Count.EqualTo(1)); @@ -185,12 +185,12 @@ public void UndoMergeMultiChildTest() Assert.That(_wordRepo.GetWord(ProjId, id).Result, Is.Not.Null); mergeWords.Children.Add(new MergeSourceWord { SrcWordId = id }); } - Assert.That(_wordRepo.GetFrontier(ProjId).Result, Has.Count.EqualTo(numberOfChildren)); + Assert.That(_wordRepo.GetAllFrontier(ProjId).Result, Has.Count.EqualTo(numberOfChildren)); var mergeWordsList = new List { mergeWords }; var newWords = _mergeService.Merge(ProjId, UserId, mergeWordsList).Result; - Assert.That(_wordRepo.GetFrontier(ProjId).Result, Has.Count.EqualTo(1)); + Assert.That(_wordRepo.GetAllFrontier(ProjId).Result, Has.Count.EqualTo(1)); var childIds = mergeWords.Children.Select(word => word.SrcWordId).ToList(); var parentIds = new List { newWords[0].Id }; @@ -198,7 +198,7 @@ public void UndoMergeMultiChildTest() var undo = _mergeService.UndoMerge(ProjId, UserId, mergedWord).Result; Assert.That(undo, Is.True); - var frontierWords = _wordRepo.GetFrontier(ProjId).Result; + var frontierWords = _wordRepo.GetAllFrontier(ProjId).Result; var frontierWordIds = frontierWords.Select(word => word.Id).ToList(); Assert.That(frontierWords, Has.Count.EqualTo(numberOfChildren)); diff --git a/Backend.Tests/Services/WordServiceTests.cs b/Backend.Tests/Services/WordServiceTests.cs index 6938979699..5b207fcfc7 100644 --- a/Backend.Tests/Services/WordServiceTests.cs +++ b/Backend.Tests/Services/WordServiceTests.cs @@ -43,7 +43,7 @@ public void TestCreateMultipleWords() { _ = _wordService.Create(UserId, [new() { ProjectId = ProjId }, new() { ProjectId = ProjId }]).Result; Assert.That(_wordRepo.GetAllWords(ProjId).Result, Has.Count.EqualTo(2)); - Assert.That(_wordRepo.GetFrontier(ProjId).Result, Has.Count.EqualTo(2)); + Assert.That(_wordRepo.GetAllFrontier(ProjId).Result, Has.Count.EqualTo(2)); } [Test] @@ -93,7 +93,7 @@ public void TestUpdateReplacesFrontierWord() var oldId = word.Id; word.Vernacular = "NewVern"; Assert.That(_wordService.Update(ProjId, UserId, oldId, word).Result, Is.EqualTo(word.Id)); - var frontier = _wordRepo.GetFrontier(ProjId).Result; + var frontier = _wordRepo.GetAllFrontier(ProjId).Result; Assert.That(frontier, Has.Count.EqualTo(1)); var newWord = frontier.First(); Assert.That(newWord.Id, Is.Not.EqualTo(oldId)); @@ -131,7 +131,7 @@ public void TestRestoreFrontierWordsFrontierWordFalse() { var wordNoFrontier = _wordRepo.Add(new Word { ProjectId = ProjId }).Result; var wordYesFrontier = _wordRepo.Create(new Word { ProjectId = ProjId }).Result; - Assert.That(_wordRepo.GetFrontier(ProjId).Result, Has.Count.EqualTo(1)); + Assert.That(_wordRepo.GetAllFrontier(ProjId).Result, Has.Count.EqualTo(1)); Assert.That( _wordService.RestoreFrontierWords(ProjId, [wordNoFrontier.Id, wordYesFrontier.Id]).Result, Is.False); } @@ -141,9 +141,9 @@ public void TestRestoreFrontierWordsTrue() { var word1 = _wordRepo.Add(new Word { ProjectId = ProjId }).Result; var word2 = _wordRepo.Add(new Word { ProjectId = ProjId }).Result; - Assert.That(_wordRepo.GetFrontier(ProjId).Result, Is.Empty); + Assert.That(_wordRepo.GetAllFrontier(ProjId).Result, Is.Empty); Assert.That(_wordService.RestoreFrontierWords(ProjId, [word1.Id, word2.Id]).Result, Is.True); - Assert.That(_wordRepo.GetFrontier(ProjId).Result, Has.Count.EqualTo(2)); + Assert.That(_wordRepo.GetAllFrontier(ProjId).Result, Has.Count.EqualTo(2)); } [Test] diff --git a/Backend/Controllers/AudioController.cs b/Backend/Controllers/AudioController.cs index d25175efd4..18122fdb70 100644 --- a/Backend/Controllers/AudioController.cs +++ b/Backend/Controllers/AudioController.cs @@ -123,7 +123,7 @@ public async Task UploadAudioFile( return BadRequest("Empty File"); } - var word = await _wordRepo.GetWord(projectId, wordId); + var word = await _wordRepo.GetFrontier(projectId, wordId); if (word is null) { return NotFound($"wordId: {wordId}"); diff --git a/Backend/Controllers/WordController.cs b/Backend/Controllers/WordController.cs index dcdc741c8b..1d381bf583 100644 --- a/Backend/Controllers/WordController.cs +++ b/Backend/Controllers/WordController.cs @@ -110,7 +110,7 @@ public async Task GetProjectFrontierWords(string projectId) { return Forbid(); } - return Ok(await _wordRepo.GetFrontier(projectId)); + return Ok(await _wordRepo.GetAllFrontier(projectId)); } /// Checks if Frontier has in specified . @@ -191,7 +191,7 @@ public async Task UpdateDuplicate( } word.ProjectId = projectId; - var duplicatedWord = await _wordRepo.GetWord(word.ProjectId, dupId); + var duplicatedWord = await _wordRepo.GetFrontier(word.ProjectId, dupId); if (duplicatedWord is null) { return NotFound(); @@ -241,7 +241,7 @@ public async Task UpdateWord( { return Forbid(); } - var document = await _wordRepo.GetWord(projectId, wordId); + var document = await _wordRepo.GetFrontier(projectId, wordId); if (document is null) { return NotFound(); diff --git a/Backend/Interfaces/IWordRepository.cs b/Backend/Interfaces/IWordRepository.cs index 1f0be2c4d3..5cb78e4a4d 100644 --- a/Backend/Interfaces/IWordRepository.cs +++ b/Backend/Interfaces/IWordRepository.cs @@ -18,7 +18,8 @@ public interface IWordRepository Task IsInFrontier(string projectId, string wordId); Task AreInFrontier(string projectId, List wordIds, int count); Task GetFrontierCount(string projectId); - Task> GetFrontier(string projectId); + Task> GetAllFrontier(string projectId); + Task GetFrontier(string projectId, string wordId, string? audioFileName = null); Task> GetFrontierWithVernacular(string projectId, string vernacular); Task AddFrontier(Word word); Task> AddFrontier(List words); diff --git a/Backend/Repositories/WordRepository.cs b/Backend/Repositories/WordRepository.cs index ddaa873582..128185d904 100644 --- a/Backend/Repositories/WordRepository.cs +++ b/Backend/Repositories/WordRepository.cs @@ -229,13 +229,25 @@ public async Task GetFrontierCount(string projectId) } /// Finds all s in the Frontier for specified - public async Task> GetFrontier(string projectId) + public async Task> GetAllFrontier(string projectId) { using var activity = OtelService.StartActivityWithTag(otelTagName, "getting all Frontier words"); return await _frontier.Find(GetAllProjectWordsFilter(projectId)).ToListAsync(); } + /// Gets a specified from the Frontier + /// The word, or null if not found. + public async Task GetFrontier(string projectId, string wordId, string? audioFileName = null) + { + using var activity = OtelService.StartActivityWithTag(otelTagName, "getting a word from Frontier"); + + return string.IsNullOrEmpty(audioFileName) + ? await _frontier.Find(GetProjectWordFilter(projectId, wordId)).FirstOrDefaultAsync() + : await _frontier.Find(GetProjectWordWithAudioFilter(projectId, wordId, audioFileName)) + .FirstOrDefaultAsync(); + } + /// Finds all s in Frontier of specified project with specified vern public async Task> GetFrontierWithVernacular(string projectId, string vernacular) { diff --git a/Backend/Services/LiftService.cs b/Backend/Services/LiftService.cs index b00dbea0af..74ef22ebd6 100644 --- a/Backend/Services/LiftService.cs +++ b/Backend/Services/LiftService.cs @@ -282,7 +282,7 @@ public async Task LiftExport(string projectId, IProjectRepository projRe // Get every word with all of its information. var allWords = await wordRepo.GetAllWords(projectId); - var frontier = await wordRepo.GetFrontier(projectId); + var frontier = await wordRepo.GetAllFrontier(projectId); var activeWords = frontier.Where( x => x.Senses.Any(s => s.Accessibility == Status.Active || s.Accessibility == Status.Protected)).ToList(); var hasFlags = activeWords.Any(w => w.Flag.Active); diff --git a/Backend/Services/MergeService.cs b/Backend/Services/MergeService.cs index b4c0149e2a..b86d5d6ba9 100644 --- a/Backend/Services/MergeService.cs +++ b/Backend/Services/MergeService.cs @@ -307,7 +307,7 @@ public async Task UpdateMergeBlacklist(string projectId) { return 0; } - var frontierWordIds = (await _wordRepo.GetFrontier(projectId)).Select(word => word.Id); + var frontierWordIds = (await _wordRepo.GetAllFrontier(projectId)).Select(word => word.Id); var updateCount = 0; foreach (var entry in oldBlacklist) { @@ -346,7 +346,7 @@ public async Task UpdateMergeGraylist(string projectId) { return 0; } - var frontierWordIds = (await _wordRepo.GetFrontier(projectId)).Select(word => word.Id); + var frontierWordIds = (await _wordRepo.GetAllFrontier(projectId)).Select(word => word.Id); var updateCount = 0; foreach (var entry in oldGraylist) { @@ -402,7 +402,7 @@ public async Task>> GetGraylistEntries( { return []; } - var frontier = await _wordRepo.GetFrontier(projectId); + var frontier = await _wordRepo.GetAllFrontier(projectId); var wordLists = new List> { Capacity = maxLists }; foreach (var entry in graylist) { @@ -443,7 +443,7 @@ public async Task>> GetPotentialDuplicates(string projectId, int { var dupFinder = new DuplicateFinder(maxInList, maxLists, 2); - var collection = await _wordRepo.GetFrontier(projectId); + var collection = await _wordRepo.GetAllFrontier(projectId); async Task isUnavailableSet(List wordIds) => (await IsInMergeBlacklist(projectId, wordIds, userId)) || (await IsInMergeGraylist(projectId, wordIds, userId)); diff --git a/Backend/Services/StatisticsService.cs b/Backend/Services/StatisticsService.cs index 2ad4d7a829..f51a897ecc 100644 --- a/Backend/Services/StatisticsService.cs +++ b/Backend/Services/StatisticsService.cs @@ -41,7 +41,7 @@ public async Task> GetSemanticDomainCounts(string proj var hashMap = new Dictionary(); var domainTreeNodeList = await _domainRepo.GetAllSemanticDomainTreeNodes(lang); - var wordList = await _wordRepo.GetFrontier(projectId); + var wordList = await _wordRepo.GetAllFrontier(projectId); if (domainTreeNodeList is null || domainTreeNodeList.Count == 0 || wordList.Count == 0) { @@ -74,7 +74,7 @@ public async Task> GetWordsPerDayPerUserCounts(str { using var activity = OtelService.StartActivityWithTag(otelTagName, "getting words per day per user counts"); - var wordList = await _wordRepo.GetFrontier(projectId); + var wordList = await _wordRepo.GetAllFrontier(projectId); var shortTimeDictionary = new Dictionary(); var userNameIdDictionary = new Dictionary(); @@ -140,7 +140,7 @@ public async Task GetProgressEstimationLineChartRoot(string proje using var activity = OtelService.StartActivityWithTag(otelTagName, "getting progress estimation line chart root"); var LineChartData = new ChartRootData(); - var wordList = await _wordRepo.GetFrontier(projectId); + var wordList = await _wordRepo.GetAllFrontier(projectId); var workshopSchedule = new List(); var totalCountDictionary = new Dictionary(); @@ -308,7 +308,7 @@ public async Task> GetSemanticDomainUserCounts(str { using var activity = OtelService.StartActivityWithTag(otelTagName, "getting semantic domain user counts"); - var wordList = await _wordRepo.GetFrontier(projectId); + var wordList = await _wordRepo.GetAllFrontier(projectId); var resUserMap = new Dictionary(); // Get all users of the project From 363b527d7c3c2f49feea4e63f265ab6de8d8f131 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Mon, 16 Feb 2026 14:43:14 -0500 Subject: [PATCH 2/2] Use GetFrontier in place missed during merge --- Backend/Services/WordService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Backend/Services/WordService.cs b/Backend/Services/WordService.cs index 79ae993b6a..75ae36bcff 100644 --- a/Backend/Services/WordService.cs +++ b/Backend/Services/WordService.cs @@ -119,8 +119,8 @@ public async Task RestoreFrontierWords(string projectId, List word using var activity = OtelService.StartActivityWithTag(otelTagName, "updating a word in Frontier"); var oldWordId = word.Id; // Capture the id in case of changes. - var oldWord = await _wordRepo.GetWord(word.ProjectId, oldWordId); - if (oldWord is null || !await _wordRepo.IsInFrontier(word.ProjectId, oldWordId)) + var oldWord = await _wordRepo.GetFrontier(word.ProjectId, oldWordId); + if (oldWord is null) { return null; }