1
+ namespace OJS . Workers . ExecutionStrategies . NodeJs . Typescript ;
2
+
3
+ using Microsoft . Extensions . Logging ;
4
+ using System . Collections . Generic ;
5
+ using System . Threading . Tasks ;
6
+ using System . Linq ;
7
+ using OJS . Workers . Common ;
8
+ using OJS . Workers . Common . Extensions ;
9
+ using OJS . Workers . Common . Helpers ;
10
+ using OJS . Workers . Common . Models ;
11
+ using OJS . Workers . Compilers ;
12
+ using OJS . Workers . ExecutionStrategies . Models ;
13
+ using OJS . Workers . Executors ;
14
+
15
+ public class TypeScriptV20PreprocessExecuteAndRunUnitTestsWithMochaExecutionStrategy < TSettings > (
16
+ IOjsSubmission submission ,
17
+ IProcessExecutorFactory processExecutorFactory ,
18
+ IExecutionStrategySettingsProvider settingsProvider ,
19
+ ILogger < BaseExecutionStrategy < TSettings > > logger ,
20
+ ICompilerFactory compilerFactory )
21
+ : NodeJsPreprocessExecuteAndRunUnitTestsWithMochaExecutionStrategy < TSettings > ( submission , processExecutorFactory ,
22
+ settingsProvider , logger )
23
+ where TSettings : NodeJsPreprocessExecuteAndRunUnitTestsWithMochaExecutionStrategySettings
24
+ {
25
+ protected const string TestsPlaceholder = "/*#testsCode*/" ;
26
+ protected const string UserCodePlaceholder = "/*#userCode#*/" ;
27
+ protected const string SolutionSkeletonPlaceholder = "/*#solutionSkeleton#*/" ;
28
+
29
+ private string TypeScriptTemplateContent => @$ "
30
+ // Imports
31
+ // @ts-ignore
32
+ const chai = require('{ this . Settings . ChaiModulePath } ');
33
+ // @ts-ignore
34
+ const sinon = require('{ this . Settings . SinonModulePath } ');
35
+ // @ts-ignore
36
+ const sinonChai = require('{ this . Settings . SinonChaiModulePath } ');
37
+
38
+ chai.use(sinonChai);
39
+
40
+ const expect = chai.expect;
41
+ const assert = chai.assert;
42
+ const should = chai.should();
43
+
44
+ // Skeleton
45
+ { SolutionSkeletonPlaceholder }
46
+
47
+ // User code
48
+ let result = { UserCodePlaceholder }
49
+
50
+ // Tests
51
+ // @ts-ignore
52
+ describe('Tests', function () {{
53
+ { TestsPlaceholder }
54
+ }});
55
+ " ;
56
+
57
+ protected override async Task < IExecutionResult < TestResult > > ExecuteAgainstTestsInput (
58
+ IExecutionContext < TestsInputModel > executionContext ,
59
+ IExecutionResult < TestResult > result )
60
+ {
61
+ // Prepare TypeScript file with combined user code and tests
62
+ var typeScriptTemplate = this . TypeScriptTemplateContent ;
63
+ var userCode = executionContext . Code . Trim ( ) ;
64
+ typeScriptTemplate = typeScriptTemplate
65
+ . Replace ( SolutionSkeletonPlaceholder , executionContext . Input . TaskSkeletonAsString )
66
+ . Replace ( UserCodePlaceholder , userCode ) ;
67
+
68
+ // Process each test and wrap it in an it() block with @ts-ignore
69
+ if ( executionContext . Input . Tests . Any ( ) )
70
+ {
71
+ var formattedTests = FormatTests ( executionContext . Input . Tests ) ;
72
+ typeScriptTemplate = typeScriptTemplate . Replace ( TestsPlaceholder , formattedTests ) ;
73
+ }
74
+
75
+ // Save the combined TypeScript file
76
+ var tsCodeSavePath = FileHelpers . SaveStringToTempFile ( this . WorkingDirectory , typeScriptTemplate ) ;
77
+
78
+ // Run TypeScript compiler to generate JavaScript
79
+ var compiler = compilerFactory . CreateCompiler ( executionContext . CompilerType , this . Type ) ;
80
+ var compilerPath = compilerFactory . GetCompilerPath ( executionContext . CompilerType , this . Type ) ;
81
+ var compilerResult = compiler . Compile ( compilerPath , tsCodeSavePath , executionContext . AdditionalCompilerArguments ) ;
82
+
83
+ if ( ! compilerResult . IsCompiledSuccessfully )
84
+ {
85
+ result . IsCompiledSuccessfully = false ;
86
+ result . CompilerComment = compilerResult . CompilerComment ;
87
+ return result ;
88
+ }
89
+
90
+ // Execute tests using Node.js on the generated JavaScript file
91
+ var executor = this . CreateRestrictedExecutor ( ) ;
92
+ var checker = executionContext . Input . GetChecker ( ) ;
93
+
94
+ var testResults = await this . ProcessTests (
95
+ executionContext ,
96
+ executor ,
97
+ checker ,
98
+ compilerResult . OutputFile ) ;
99
+ result . Results . AddRange ( testResults ) ;
100
+
101
+ return result ;
102
+ }
103
+
104
+ private static string FormatTests ( IEnumerable < TestContext > tests )
105
+ {
106
+ var formattedTests = new List < string > ( ) ;
107
+ var testCounter = 1 ;
108
+
109
+ foreach ( var test in tests )
110
+ {
111
+ // Use simple sequential test names
112
+ var testName = $ "Test{ testCounter } ";
113
+ var testContent = test . Input . Trim ( ) ;
114
+
115
+ // Format the test with proper it() wrapper
116
+ var formattedTest = $@ "
117
+ // @ts-ignore
118
+ it('{ testName } ', function () {{
119
+ { testContent }
120
+ }})" ;
121
+
122
+ formattedTests . Add ( formattedTest ) ;
123
+ testCounter ++ ;
124
+ }
125
+
126
+ // Join all formatted tests
127
+ return string . Join ( "\n " , formattedTests ) ;
128
+ }
129
+
130
+ protected override async Task < List < TestResult > > ProcessTests (
131
+ IExecutionContext < TestsInputModel > executionContext ,
132
+ IExecutor executor ,
133
+ IChecker checker ,
134
+ string codeSavePath )
135
+ {
136
+ var testResults = new List < TestResult > ( ) ;
137
+
138
+ // Configure arguments for Mocha with Node.js
139
+ var arguments = new List < string >
140
+ {
141
+ this . Settings . MochaModulePath ,
142
+ codeSavePath ,
143
+ "--reporter" , "json"
144
+ } ;
145
+ arguments . AddRange ( this . AdditionalExecutionArguments ) ;
146
+
147
+ // Execute Mocha with Node.js
148
+ var processExecutionResult = await executor . Execute (
149
+ this . Settings . NodeJsExecutablePath ,
150
+ executionContext . TimeLimit ,
151
+ executionContext . MemoryLimit ,
152
+ string . Empty ,
153
+ arguments ) ;
154
+
155
+ // Parse the results
156
+ var mochaResult = JsonExecutionResult . Parse ( processExecutionResult . ReceivedOutput ) ;
157
+
158
+ // Map results to each test
159
+ var currentTest = 0 ;
160
+ foreach ( var test in executionContext . Input . Tests )
161
+ {
162
+ var message = "yes" ;
163
+ if ( ! string . IsNullOrEmpty ( mochaResult . Error ) )
164
+ {
165
+ message = mochaResult . Error ;
166
+ }
167
+ else if ( currentTest < mochaResult . TestErrors . Count && mochaResult . TestErrors [ currentTest ] != null )
168
+ {
169
+ message = $ "Test failed: { mochaResult . TestErrors [ currentTest ] } ";
170
+ }
171
+
172
+ var testResult = CheckAndGetTestResult (
173
+ test ,
174
+ processExecutionResult ,
175
+ checker ,
176
+ message ) ;
177
+
178
+ currentTest ++ ;
179
+ testResults . Add ( testResult ) ;
180
+ }
181
+
182
+ return testResults ;
183
+ }
184
+ }
0 commit comments