From bbb823355b6e35ebe4f2abc8dfb92c306b3491dc Mon Sep 17 00:00:00 2001 From: Georgi Georgiev Date: Mon, 22 Dec 2025 10:30:47 +0200 Subject: [PATCH 1/7] 1748 support typescript exam projects with bundling step before running mochasinon tests (#1671) 1748 support typescript exam projects with bundling step before running mochasinon tests (#1671) closes https://github.com/SoftUni-Internal/exam-systems-issues/issues/1748 --- Docker/worker_base/js/v20/package.json | 3 +- .../OJS.Servers.Worker/appsettings.json | 1 + .../Models/ExecutionStrategyType.cs | 1 + ...tProjectMochaUnitTestsExecutionStrategy.cs | 68 +++++++++++++++++++ .../ExecutionStrategyFactory.cs | 7 ++ .../ExecutionStrategySettingsProvider.cs | 13 ++++ .../Configuration/OjsWorkersConfig.cs | 3 + 7 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/Typescript/TypeScriptProjectMochaUnitTestsExecutionStrategy.cs diff --git a/Docker/worker_base/js/v20/package.json b/Docker/worker_base/js/v20/package.json index fdd34dc6dd..8170d1fd70 100644 --- a/Docker/worker_base/js/v20/package.json +++ b/Docker/worker_base/js/v20/package.json @@ -23,6 +23,7 @@ "sinon": "^17.0.1", "sinon-chai": "^3.7.0", "underscore": "^1.13.6", - "http-server": "14.1.1" + "http-server": "14.1.1", + "esbuild": "^0.27.1" } } diff --git a/Servers/Worker/OJS.Servers.Worker/appsettings.json b/Servers/Worker/OJS.Servers.Worker/appsettings.json index 9a05afdf64..d8b77d1272 100644 --- a/Servers/Worker/OJS.Servers.Worker/appsettings.json +++ b/Servers/Worker/OJS.Servers.Worker/appsettings.json @@ -66,6 +66,7 @@ "UnderscoreModulePath": "{nodeResourcesPath}/node_modules/underscore", "MochaModulePath": "{nodeResourcesPath}/node_modules/mocha/bin/_mocha", "ChaiModulePath": "{nodeResourcesPath}/node_modules/chai", + "ESBuildModulePath": "{nodeResourcesPath}/node_modules/esbuild/bin/esbuild", "PlaywrightModulePath": "{nodeResourcesPath}/node_modules/playwright", "PlaywrightChromiumModulePath": "{nodeResourcesPath}/node_modules/playwright-chromium", "JsDomModulePath": "{nodeResourcesPath}/node_modules/jsdom", diff --git a/Services/Common/OJS.Workers/OJS.Workers.Common/Models/ExecutionStrategyType.cs b/Services/Common/OJS.Workers/OJS.Workers.Common/Models/ExecutionStrategyType.cs index 5b153834c0..5d87fe9bc5 100644 --- a/Services/Common/OJS.Workers/OJS.Workers.Common/Models/ExecutionStrategyType.cs +++ b/Services/Common/OJS.Workers/OJS.Workers.Common/Models/ExecutionStrategyType.cs @@ -76,5 +76,6 @@ public enum ExecutionStrategyType DotNetCore8UnitTestsExecutionStrategy = 79, PythonDjangoOrmParallelExecutionStrategy = 80, NodeJsV20PreprocessExecuteAndRunAllUnitTestsWithMocha = 81, + TypeScriptV20ProjectMochaUnitTestsExecutionStrategy = 82, } } diff --git a/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/Typescript/TypeScriptProjectMochaUnitTestsExecutionStrategy.cs b/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/Typescript/TypeScriptProjectMochaUnitTestsExecutionStrategy.cs new file mode 100644 index 0000000000..83d040e68d --- /dev/null +++ b/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/Typescript/TypeScriptProjectMochaUnitTestsExecutionStrategy.cs @@ -0,0 +1,68 @@ +namespace OJS.Workers.ExecutionStrategies.NodeJs.Typescript; + +using Common; +using Common.Helpers; +using Executors; +using Microsoft.Extensions.Logging; +using Models; + +public class TypeScriptProjectMochaUnitTestsExecutionStrategy( + IOjsSubmission submission, + IProcessExecutorFactory processExecutorFactory, + IExecutionStrategySettingsProvider settingsProvider, + ILogger> logger) + : NodeJsPreprocessExecuteAndRunAllUnitTestsWithMochaExecutionStrategy( + submission, + processExecutorFactory, + settingsProvider, + logger) + where TSettings : TypeScriptProjectMochaUnitTestsExecutionStrategySettings +{ + protected override async Task> ExecuteAgainstTestsInput(IExecutionContext executionContext, IExecutionResult result, + CancellationToken cancellationToken = default) + { + SaveZipSubmission(executionContext.FileContent, this.WorkingDirectory); + + var executor = this.CreateStandardExecutor(); + var bundleResult = await executor.Execute( + this.Settings.EsBuildModulePath, + executionContext.TimeLimit, + executionContext.MemoryLimit, + executionArguments: ["src/index.ts", "--bundle", "--platform=node", "--format=cjs", "--target=node21", "--packages=external", "--outfile=dist/app.bundle.js"], + workingDirectory: this.WorkingDirectory, + cancellationToken: cancellationToken); + + if (bundleResult.ExitCode != 0) + { + result.IsCompiledSuccessfully = false; + result.CompilerComment = bundleResult.ErrorOutput; + return result; + } + + var bundlePath = FileHelpers.BuildPath(this.WorkingDirectory, "dist", "app.bundle.js"); + + executionContext.Code = FileHelpers.ReadFile(bundlePath); + + return await base.ExecuteAgainstTestsInput(executionContext, result, cancellationToken); + } +} + +public record TypeScriptProjectMochaUnitTestsExecutionStrategySettings( + int BaseTimeUsed, + int BaseMemoryUsed, + string NodeJsExecutablePath, + string UnderscoreModulePath, + string MochaModulePath, + string ChaiModulePath, + string SinonModulePath, + string SinonChaiModulePath, + string EsBuildModulePath) + : NodeJsPreprocessExecuteAndRunUnitTestsWithMochaExecutionStrategySettings( + BaseTimeUsed, + BaseMemoryUsed, + NodeJsExecutablePath, + UnderscoreModulePath, + MochaModulePath, + ChaiModulePath, + SinonModulePath, + SinonChaiModulePath); \ No newline at end of file diff --git a/Services/Worker/OJS.Services.Worker.Business/Implementations/ExecutionStrategyFactory.cs b/Services/Worker/OJS.Services.Worker.Business/Implementations/ExecutionStrategyFactory.cs index fcc44f43c2..274d5fae57 100644 --- a/Services/Worker/OJS.Services.Worker.Business/Implementations/ExecutionStrategyFactory.cs +++ b/Services/Worker/OJS.Services.Worker.Business/Implementations/ExecutionStrategyFactory.cs @@ -190,6 +190,13 @@ public IExecutionStrategy CreateExecutionStrategy(IOjsSubmission submission) loggerFactory.CreateStrategyLogger>(submissionId, verbosely, logFileMaxBytes), compilerFactory); break; + case ExecutionStrategyType.TypeScriptV20ProjectMochaUnitTestsExecutionStrategy: + executionStrategy = new TypeScriptProjectMochaUnitTestsExecutionStrategy( + submission, + processExecutorFactory, + executionStrategySettingsProvider, + loggerFactory.CreateStrategyLogger>(submissionId, verbosely, logFileMaxBytes)); + break; case ExecutionStrategyType.NodeJsPreprocessExecuteAndRunUnitTestsWithMocha: case ExecutionStrategyType.NodeJsV20PreprocessExecuteAndRunUnitTestsWithMocha: executionStrategy = new NodeJsPreprocessExecuteAndRunUnitTestsWithMochaExecutionStrategy( diff --git a/Services/Worker/OJS.Services.Worker.Business/Implementations/ExecutionStrategySettingsProvider.cs b/Services/Worker/OJS.Services.Worker.Business/Implementations/ExecutionStrategySettingsProvider.cs index 2f0298d8cd..30441332ac 100644 --- a/Services/Worker/OJS.Services.Worker.Business/Implementations/ExecutionStrategySettingsProvider.cs +++ b/Services/Worker/OJS.Services.Worker.Business/Implementations/ExecutionStrategySettingsProvider.cs @@ -70,6 +70,19 @@ ExecutionStrategyType.NodeJsPreprocessExecuteAndCheck or this.GetNodeResourcePath(executionStrategyType, this.settings.SinonModulePath), this.GetNodeResourcePath(executionStrategyType, this.settings.SinonChaiModulePath)) + as TSettings, + ExecutionStrategyType.TypeScriptV20ProjectMochaUnitTestsExecutionStrategy => new + TypeScriptProjectMochaUnitTestsExecutionStrategySettings( + GetBaseTimeUsed(submission, this.settings.NodeJsBaseTimeUsedInMilliseconds * 2), + GetBaseMemoryUsed(submission, this.settings.NodeJsBaseMemoryUsedInBytes), + this.GetNodeJsExecutablePath(executionStrategyType), + this.GetNodeResourcePath(executionStrategyType, this.settings.UnderscoreModulePath), + this.GetNodeResourcePath(executionStrategyType, this.settings.MochaModulePath), + this.GetNodeResourcePath(executionStrategyType, this.settings.ChaiModulePath), + this.GetNodeResourcePath(executionStrategyType, this.settings.SinonModulePath), + this.GetNodeResourcePath(executionStrategyType, this.settings.SinonChaiModulePath), + this.GetNodeResourcePath(executionStrategyType, this.settings.EsBuildModulePath)) + as TSettings, ExecutionStrategyType.JavaPreprocessCompileExecuteAndCheck or ExecutionStrategyType.Java21PreprocessCompileExecuteAndCheck => new diff --git a/Services/Worker/OJS.Services.Worker.Models/Configuration/OjsWorkersConfig.cs b/Services/Worker/OJS.Services.Worker.Models/Configuration/OjsWorkersConfig.cs index 9abf86e1e9..4c6d85784e 100644 --- a/Services/Worker/OJS.Services.Worker.Models/Configuration/OjsWorkersConfig.cs +++ b/Services/Worker/OJS.Services.Worker.Models/Configuration/OjsWorkersConfig.cs @@ -127,6 +127,9 @@ public class OjsWorkersConfig : BaseConfig [Required] public string SinonChaiModulePath { get; set; } = string.Empty; + [Required] + public string EsBuildModulePath { get; set; } = string.Empty; + [Required] public string UnderscoreModulePath { get; set; } = string.Empty; From cfbd3009d27c97bb6906a5e007aa89686068e45f Mon Sep 17 00:00:00 2001 From: {{name}} <{{email}}> Date: Sat, 21 Mar 2026 02:04:03 +0200 Subject: [PATCH 2/7] Fixed tests structure to eliminate nested mocha it() clauses and improved error messages. --- .../Models/JsonExecutionResult.cs | 9 ++++++--- ...xecuteAndRunAllUnitTestsWithMochaExecutionStrategy.cs | 7 +------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/Models/JsonExecutionResult.cs b/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/Models/JsonExecutionResult.cs index 36ca92e12a..089819400d 100644 --- a/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/Models/JsonExecutionResult.cs +++ b/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/Models/JsonExecutionResult.cs @@ -10,6 +10,9 @@ namespace OJS.Workers.ExecutionStrategies.Models public class JsonExecutionResult { private const string InvalidJsonReplace = "]}[},!^@,Invalid,!^@,{]{["; + private const string MissingJsonStructureError = "Invalid console output! Please make sure there are no console.log statements in the solution. The system expects JSON output from Mocha test results."; + private const string MissingPassingFieldError = "Invalid console output! Please make sure there are no console.log statements in the solution. Missing or invalid 'passing' array in test results."; + private const string MissingFailuresFieldError = "Invalid console output! Please make sure there are no console.log statements in the solution. Missing or invalid 'failures' array with 'err.message' fields."; public IList TestErrors { get; set; } @@ -68,7 +71,7 @@ public static JsonExecutionResult Parse(string result, bool forceErrorExtracting } catch { - error = "Invalid console output!"; + error = MissingJsonStructureError; } var testsIndexes = new List(); @@ -80,7 +83,7 @@ public static JsonExecutionResult Parse(string result, bool forceErrorExtracting } catch { - error = "Invalid console output!"; + error = MissingPassingFieldError; } } @@ -97,7 +100,7 @@ public static JsonExecutionResult Parse(string result, bool forceErrorExtracting } catch { - error = "Invalid console output!"; + error = MissingFailuresFieldError; } } diff --git a/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/NodeJsPreprocessExecuteAndRunAllUnitTestsWithMochaExecutionStrategy.cs b/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/NodeJsPreprocessExecuteAndRunAllUnitTestsWithMochaExecutionStrategy.cs index feb3b1f205..8b3917c493 100644 --- a/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/NodeJsPreprocessExecuteAndRunAllUnitTestsWithMochaExecutionStrategy.cs +++ b/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/NodeJsPreprocessExecuteAndRunAllUnitTestsWithMochaExecutionStrategy.cs @@ -107,23 +107,18 @@ protected override async Task> ExecuteAgainstTestsI protected static string FormatTests(IEnumerable tests, bool isTypeScript) { var formattedTests = new List(); - var testCounter = 1; foreach (var test in tests) { // Use simple sequential test names - var testName = $"Test{testCounter}"; var testContent = test.Input.Trim(); // Format the test with proper it() wrapper var formattedTest = $@" {(isTypeScript ? "// @ts-ignore" : "")} - it('{testName}', function () {{ - {testContent} - }})"; + {testContent}"; formattedTests.Add(formattedTest); - testCounter++; } // Join all formatted tests From 8616a0877e92bdf75a3dd1cf6870016c16edecfc Mon Sep 17 00:00:00 2001 From: "DESKTOP-T0LU3CP\\User1" Date: Tue, 24 Mar 2026 16:13:19 +0200 Subject: [PATCH 3/7] Fixed value toString throws for text in columns https://github.com/SoftUni-Internal/exam-systems-issues/issues/1750 --- .../contest-categories/contestCategoriesGridColumns.tsx | 4 ++-- .../pages/administration-new/contests/contestsGridColumns.tsx | 4 ++-- .../administration-new/exam-groups/examGroupsGridColumns.tsx | 2 +- .../participants/participantsGridColumns.tsx | 2 +- .../pages/administration-new/problems/problemGridColumns.tsx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/contest-categories/contestCategoriesGridColumns.tsx b/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/contest-categories/contestCategoriesGridColumns.tsx index 988e8c0a8e..1f42d6fa78 100644 --- a/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/contest-categories/contestCategoriesGridColumns.tsx +++ b/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/contest-categories/contestCategoriesGridColumns.tsx @@ -70,7 +70,7 @@ const categoriesFilterableColumns: AdministrationGridColDef[] = [ categoryId: params.row.id, categoryName: params.row.name, })} - text={params.value.toString()} + text={params.value?.toString()} /> , }, @@ -100,7 +100,7 @@ const categoriesFilterableColumns: AdministrationGridColDef[] = [ categoryId: params.row.parentId, categoryName: params.row.parent, })} - text={params.value.toString()} + text={params.value?.toString()} /> , }, diff --git a/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/contests/contestsGridColumns.tsx b/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/contests/contestsGridColumns.tsx index 1ce9a6a8fa..3cb4283fc0 100644 --- a/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/contests/contestsGridColumns.tsx +++ b/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/contests/contestsGridColumns.tsx @@ -53,7 +53,7 @@ const contestFilterableColumns: AdministrationGridColDef[] = [ contestId: params.row.id, contestName: params.row.name, })} - text={params.value.toString()} + text={params.value?.toString()} /> , }, @@ -73,7 +73,7 @@ const contestFilterableColumns: AdministrationGridColDef[] = [ contestId: params.row.id, contestName: params.row.name, })} - text={params.value.toString()} + text={params.value?.toString()} /> , }, diff --git a/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/exam-groups/examGroupsGridColumns.tsx b/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/exam-groups/examGroupsGridColumns.tsx index 3853035f98..964df2eb53 100644 --- a/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/exam-groups/examGroupsGridColumns.tsx +++ b/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/exam-groups/examGroupsGridColumns.tsx @@ -50,7 +50,7 @@ const examGroupsFilterableColumns: AdministrationGridColDef[] = [ contestId: params.row.contestId, contestName: params.row.contestName, })} - text={params.value.toString()} + text={params.value?.toString()} /> , }, diff --git a/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/participants/participantsGridColumns.tsx b/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/participants/participantsGridColumns.tsx index af93a9bf08..b221d488a6 100644 --- a/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/participants/participantsGridColumns.tsx +++ b/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/participants/participantsGridColumns.tsx @@ -61,7 +61,7 @@ const participantsFilteringColumns: AdministrationGridColDef[] = [ contestId: params.row.contestId, contestName: params.row.contestName, })} - text={params.value.toString()} + text={params.value?.toString()} /> , }, diff --git a/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/problems/problemGridColumns.tsx b/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/problems/problemGridColumns.tsx index 0228407278..feaddf0d95 100644 --- a/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/problems/problemGridColumns.tsx +++ b/Servers/UI/OJS.Servers.Ui/ClientApp/src/pages/administration-new/problems/problemGridColumns.tsx @@ -79,7 +79,7 @@ const problemFilterableColumns: AdministrationGridColDef[] = [ contestId: params.row.contestId, contestName: params.row.contest, })} - text={params.value.toString()} + text={params.value?.toString()} /> , }, From 4699ed62476273cc655ccf0a225a23613ddde32a Mon Sep 17 00:00:00 2001 From: {{name}} <{{email}}> Date: Wed, 25 Mar 2026 23:21:13 +0200 Subject: [PATCH 4/7] Fixed contests visibility inconsistency bug --- .../ContestsBusinessService.cs | 33 +++++++++++++++---- .../IContestsDataService.cs | 2 +- .../Implementations/ContestsDataService.cs | 11 +++++-- .../Contests/ContestForListingServiceModel.cs | 3 -- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs index 31be3e37a0..64687d164e 100644 --- a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs +++ b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs @@ -13,6 +13,7 @@ namespace OJS.Services.Ui.Business.Implementations; using OJS.Services.Infrastructure.Constants; using OJS.Services.Infrastructure.Extensions; using OJS.Services.Infrastructure.Models; +using OJS.Services.Infrastructure; using OJS.Services.Ui.Business.Cache; using OJS.Services.Ui.Business.Validations.Implementations.Contests; using OJS.Services.Ui.Data; @@ -34,7 +35,8 @@ public class ContestsBusinessService( IContestParticipantsCacheService contestParticipantsCacheService, IContestsCacheService contestsCacheService, ILecturersInContestsCacheService lecturersInContestsCache, - IContestDetailsValidationService contestDetailsValidationService) + IContestDetailsValidationService contestDetailsValidationService, + IDatesService datesService) : IContestsBusinessService { public async Task> GetContestDetails(int id) @@ -328,6 +330,11 @@ public async Task GetSearchContestsByName( .MapCollection() .ToPagedListAsync(model.PageNumber, model.ItemsPerPage); + foreach (var contest in searchContests) + { + contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); + } + modelResult.Contests = searchContests; modelResult.TotalContestsCount = allContestsQueryable.Count(); @@ -342,7 +349,7 @@ public async Task> GetAllByFiltersAnd var includeHidden = model.IncludeHidden && user.IsAdmin; var pagedContests = - await contestsData.GetAllAsPageByFiltersAndSorting(model, includeHidden); + await contestsData.GetAllAsPageByFiltersAndSorting(model, includeHidden); var participantResultsByContest = new Dictionary>(); if (user.IsAuthenticated) @@ -390,6 +397,11 @@ public async Task> GetParticipatedByU participatedContests, sortAndFilterModel); + foreach (var contest in participatedContestsInPage.Items) + { + contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); + } + var participantResultsByContest = new Dictionary>(); var loggedInUser = userProviderService.GetCurrentUser(); @@ -499,10 +511,19 @@ await pagedContests.Items.ForEachAsync(c => .ToListAsync(); public async Task> GetAllParticipatedContests(string username) - => await contestsData - .GetLatestForParticipantByUsername(username) - .MapCollection() - .ToListAsync(); + { + var contests = await contestsData + .GetLatestForParticipantByUsername(username) + .MapCollection() + .ToListAsync(); + + foreach (var contest in contests) + { + contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); + } + + return contests; + } private static async Task>> MapParticipationResultsToContestsInPage( IQueryable participants) diff --git a/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs b/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs index ec1c371d88..c4d4fa2e15 100644 --- a/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs +++ b/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs @@ -18,7 +18,7 @@ Task> ApplyFiltersSortAndPagination( Task> GetAllExpired(); - Task> GetAllAsPageByFiltersAndSorting(ContestFiltersServiceModel model, bool includeHidden = false); + Task> GetAllAsPageByFiltersAndSorting(ContestFiltersServiceModel model, bool includeHidden = false); IQueryable GetLatestForParticipantByUsername(string username); diff --git a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs index 82bcb38da1..645414594f 100644 --- a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs +++ b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs @@ -39,7 +39,7 @@ public async Task> GetAllExpired() public IQueryable GetAllVisible() => this.GetQuery(c => c.IsVisible || c.VisibleFrom <= this.dates.GetUtcNow()); - public async Task> GetAllAsPageByFiltersAndSorting( + public async Task> GetAllAsPageByFiltersAndSorting( ContestFiltersServiceModel model, bool includeHidden = false) { @@ -53,7 +53,14 @@ public async Task> GetAllAsPageByFiltersAndSorting c.CategoryId.HasValue && model.CategoryIds.Contains(c.CategoryId.Value)); } - return await this.ApplyFiltersSortAndPagination(contests, model); + var contestsPage = await this.ApplyFiltersSortAndPagination(contests, model); + + foreach (var contest in contestsPage.Items) + { + contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= this.dates.GetUtcNow(); + } + + return contestsPage; } public IQueryable GetLatestForParticipantByUsername(string username) diff --git a/Services/UI/OJS.Services.Ui.Models/Contests/ContestForListingServiceModel.cs b/Services/UI/OJS.Services.Ui.Models/Contests/ContestForListingServiceModel.cs index ed630a9ecf..c82be95875 100644 --- a/Services/UI/OJS.Services.Ui.Models/Contests/ContestForListingServiceModel.cs +++ b/Services/UI/OJS.Services.Ui.Models/Contests/ContestForListingServiceModel.cs @@ -84,9 +84,6 @@ public void RegisterMappings(IProfileExpression configuration) .SelectMany(pg => pg.Problems) .Where(x => !x.IsDeleted) .Sum(pr => pr.MaximumPoints))) - .ForMember( - c => c.IsVisible, - opt => opt.MapFrom(s => s.IsVisible || s.VisibleFrom <= DateTime.UtcNow)) // For online contests: // In a problem group with multiple problems, compete points are derived from a single problem, // unlike practice mode where points can be accumulated from all problems across groups. From 8cd41486f464d27070e8f3594adf3ff0b6efb580 Mon Sep 17 00:00:00 2001 From: {{name}} <{{email}}> Date: Wed, 25 Mar 2026 23:31:21 +0200 Subject: [PATCH 5/7] Extracted method --- .../ContestsBusinessService.cs | 26 ++++++++++--------- .../Implementations/ContestsDataService.cs | 9 +------ 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs index 64687d164e..d45ef7f6a5 100644 --- a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs +++ b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs @@ -330,10 +330,7 @@ public async Task GetSearchContestsByName( .MapCollection() .ToPagedListAsync(model.PageNumber, model.ItemsPerPage); - foreach (var contest in searchContests) - { - contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); - } + this.SetContestVisibility(searchContests); modelResult.Contests = searchContests; modelResult.TotalContestsCount = allContestsQueryable.Count(); @@ -351,6 +348,8 @@ public async Task> GetAllByFiltersAnd var pagedContests = await contestsData.GetAllAsPageByFiltersAndSorting(model, includeHidden); + this.SetContestVisibility(pagedContests.Items); + var participantResultsByContest = new Dictionary>(); if (user.IsAuthenticated) { @@ -397,10 +396,7 @@ public async Task> GetParticipatedByU participatedContests, sortAndFilterModel); - foreach (var contest in participatedContestsInPage.Items) - { - contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); - } + this.SetContestVisibility(participatedContestsInPage.Items); var participantResultsByContest = new Dictionary>(); var loggedInUser = userProviderService.GetCurrentUser(); @@ -517,10 +513,7 @@ public async Task> GetAllParticipated .MapCollection() .ToListAsync(); - foreach (var contest in contests) - { - contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); - } + this.SetContestVisibility(contests); return contests; } @@ -586,4 +579,13 @@ private async Task GetNestedFilterCategoriesIfAny(Co official, isUserAdminOrLecturerInContest); } + + private void SetContestVisibility(IEnumerable contests) + { + var now = datesService.GetUtcNow(); + foreach (var contest in contests) + { + contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= now; + } + } } \ No newline at end of file diff --git a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs index 645414594f..270b9f997a 100644 --- a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs +++ b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs @@ -53,14 +53,7 @@ public async Task> GetAllAsPageByFilt .Where(c => c.CategoryId.HasValue && model.CategoryIds.Contains(c.CategoryId.Value)); } - var contestsPage = await this.ApplyFiltersSortAndPagination(contests, model); - - foreach (var contest in contestsPage.Items) - { - contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= this.dates.GetUtcNow(); - } - - return contestsPage; + return await this.ApplyFiltersSortAndPagination(contests, model); } public IQueryable GetLatestForParticipantByUsername(string username) From dab1223f8d44d6ca9f8da6931b617a89f33b1516 Mon Sep 17 00:00:00 2001 From: {{name}} <{{email}}> Date: Wed, 25 Mar 2026 23:34:29 +0200 Subject: [PATCH 6/7] Reverts --- .../Implementations/ContestsBusinessService.cs | 2 +- Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs | 2 +- .../Implementations/ContestsDataService.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs index d45ef7f6a5..219400c721 100644 --- a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs +++ b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs @@ -346,7 +346,7 @@ public async Task> GetAllByFiltersAnd var includeHidden = model.IncludeHidden && user.IsAdmin; var pagedContests = - await contestsData.GetAllAsPageByFiltersAndSorting(model, includeHidden); + await contestsData.GetAllAsPageByFiltersAndSorting(model, includeHidden); this.SetContestVisibility(pagedContests.Items); diff --git a/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs b/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs index c4d4fa2e15..ec1c371d88 100644 --- a/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs +++ b/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs @@ -18,7 +18,7 @@ Task> ApplyFiltersSortAndPagination( Task> GetAllExpired(); - Task> GetAllAsPageByFiltersAndSorting(ContestFiltersServiceModel model, bool includeHidden = false); + Task> GetAllAsPageByFiltersAndSorting(ContestFiltersServiceModel model, bool includeHidden = false); IQueryable GetLatestForParticipantByUsername(string username); diff --git a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs index 270b9f997a..82bcb38da1 100644 --- a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs +++ b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs @@ -39,7 +39,7 @@ public async Task> GetAllExpired() public IQueryable GetAllVisible() => this.GetQuery(c => c.IsVisible || c.VisibleFrom <= this.dates.GetUtcNow()); - public async Task> GetAllAsPageByFiltersAndSorting( + public async Task> GetAllAsPageByFiltersAndSorting( ContestFiltersServiceModel model, bool includeHidden = false) { @@ -53,7 +53,7 @@ public async Task> GetAllAsPageByFilt .Where(c => c.CategoryId.HasValue && model.CategoryIds.Contains(c.CategoryId.Value)); } - return await this.ApplyFiltersSortAndPagination(contests, model); + return await this.ApplyFiltersSortAndPagination(contests, model); } public IQueryable GetLatestForParticipantByUsername(string username) From 1ebc58721670c17289a60a601a7f7d63fc4e9125 Mon Sep 17 00:00:00 2001 From: {{name}} <{{email}}> Date: Fri, 27 Mar 2026 12:22:33 +0200 Subject: [PATCH 7/7] Removed console.logs from user code to fix parsing errors in moha process output --- ...ScriptProjectMochaUnitTestsExecutionStrategy.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/Typescript/TypeScriptProjectMochaUnitTestsExecutionStrategy.cs b/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/Typescript/TypeScriptProjectMochaUnitTestsExecutionStrategy.cs index 83d040e68d..7f1b76e64b 100644 --- a/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/Typescript/TypeScriptProjectMochaUnitTestsExecutionStrategy.cs +++ b/Services/Common/OJS.Workers/OJS.Workers.ExecutionStrategies/NodeJs/Typescript/TypeScriptProjectMochaUnitTestsExecutionStrategy.cs @@ -23,12 +23,24 @@ protected override async Task> ExecuteAgainstTestsI { SaveZipSubmission(executionContext.FileContent, this.WorkingDirectory); + var esBuildExecutionArguments = new[] + { + "src/index.ts", + "--bundle", + "--platform=node", + "--format=cjs", + "--target=node21", + "--packages=external", + "--drop:console", + "--outfile=dist/app.bundle.js" + }; + var executor = this.CreateStandardExecutor(); var bundleResult = await executor.Execute( this.Settings.EsBuildModulePath, executionContext.TimeLimit, executionContext.MemoryLimit, - executionArguments: ["src/index.ts", "--bundle", "--platform=node", "--format=cjs", "--target=node21", "--packages=external", "--outfile=dist/app.bundle.js"], + executionArguments: esBuildExecutionArguments, workingDirectory: this.WorkingDirectory, cancellationToken: cancellationToken);