public void InvokeFromStringAsync_WithTypeParameter_WithRawStringModule_IsThreadSafe() { // Arrange const string dummyArg = "success"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act var results = new ConcurrentQueue <DummyResult>(); const int numThreads = 5; var threads = new List <Thread>(); for (int i = 0; i < numThreads; i++) { var thread = new Thread(() => results.Enqueue(testSubject. InvokeFromStringAsync <DummyResult>(_dummyReturnsArgModule, args: new[] { dummyArg }).GetAwaiter().GetResult())); threads.Add(thread); thread.Start(); } foreach (Thread thread in threads) { thread.Join(); } // Assert Assert.Equal(numThreads, results.Count); foreach (DummyResult result in results) { Assert.Equal(dummyArg, result.Result); } }
public void InvokeFromStringAsync_IsThreadSafe() { // Arrange const string dummyModule = "module.exports = (callback, resultString) => callback(null, {result: resultString});"; const string dummyResultString = "success"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act var results = new ConcurrentQueue <DummyResult>(); const int numThreads = 5; var threads = new List <Thread>(); for (int i = 0; i < numThreads; i++) { var thread = new Thread(() => results.Enqueue(testSubject.InvokeFromStringAsync <DummyResult>(dummyModule, args: new[] { dummyResultString }).GetAwaiter().GetResult())); threads.Add(thread); thread.Start(); } foreach (Thread thread in threads) { thread.Join(); } // Assert Assert.Equal(numThreads, results.Count); foreach (DummyResult result in results) { Assert.Equal(dummyResultString, result.Result); } }
public void HttpNodeJSService_FileWatching_GracefulShutdownEnabled_MoveToNewProcess_Setup() { var services = new ServiceCollection(); services.AddNodeJS(); services.Configure <OutOfProcessNodeJSServiceOptions>(options => options.EnableFileWatching = true); _serviceProvider = services.BuildServiceProvider(); _httpNodeJSService = _serviceProvider.GetRequiredService <INodeJSService>() as HttpNodeJSService; // Warmup. First run starts a Node.js process. _httpNodeJSService.InvokeFromStringAsync(DUMMY_WARMUP_MODULE).GetAwaiter().GetResult(); }
public async void AllInvokeMethods_InvokeAsyncJavascriptMethods() { // Arrange const string dummyArg = "success"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act DummyResult result = await testSubject. InvokeFromStringAsync <DummyResult>("module.exports = async (resultString) => {return {result: resultString};}", args : new[] { dummyArg }).ConfigureAwait(false); // Assert Assert.Equal(dummyArg, result.Result); }
public async void InvokeFromStringAsync_InvokesJavascript() { // Arrange const string dummyResultString = "success"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act DummyResult result = await testSubject. InvokeFromStringAsync <DummyResult>("module.exports = (callback, resultString) => callback(null, {result: resultString});", args : new[] { dummyResultString }).ConfigureAwait(false); // Assert Assert.Equal(dummyResultString, result.Result); }
public async void InvokeFromStringAsync_WithTypeParameter_WithRawStringModule_InvokesFromString() { // Arrange const string dummyArg = "success"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act DummyResult result = await testSubject. InvokeFromStringAsync <DummyResult>(_dummyReturnsArgModule, args : new[] { dummyArg }).ConfigureAwait(false); // Assert Assert.Equal(dummyArg, result.Result); }
public async void TryInvokeFromCacheAsync_WithTypeParameter_InvokesFromCacheIfModuleIsCached() { // Arrange const string dummyArg = "success"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); await testSubject.InvokeFromStringAsync <DummyResult>(_dummyReturnsArgModule, DUMMY_CACHE_IDENTIFIER, args : new[] { dummyArg }).ConfigureAwait(false); // Act (bool success, DummyResult value) = await testSubject.TryInvokeFromCacheAsync <DummyResult>(DUMMY_CACHE_IDENTIFIER, args : new[] { dummyArg }).ConfigureAwait(false); // Assert Assert.True(success); Assert.Equal(dummyArg, value.Result); }
public async void AllInvokeMethods_InvokeASpecificExportIfExportNameIsNotNull() { // Arrange const string dummyArg = "success"; const string dummyExportName = "dummyExportName"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act DummyResult result = await testSubject. InvokeFromStringAsync <DummyResult>($"module.exports = {{ {dummyExportName}: (callback, resultString) => callback(null, {{result: resultString}}) }};", exportName : dummyExportName, args : new[] { dummyArg }).ConfigureAwait(false); // Assert Assert.Equal(dummyArg, result.Result); }
public async void InMemoryInvokeMethods_LoadRequiredModulesFromFilesInProjectDirectory() { // Arrange HttpNodeJSService testSubject = CreateHttpNodeJSService(projectPath: _projectPath); // Current directory is <test project path>/bin/debug/<framework> // Act int result = await testSubject.InvokeFromStringAsync <int>(@"const value = require('./dummyReturnsValueModule.js'); module.exports = (callback) => { callback(null, value); };").ConfigureAwait(false); // Assert Assert.Equal(10, result); // dummyReturnsValueModule.js just exports 10 }
public async void InvokeFromStringAsync_WithoutTypeParameter_WithModuleFactory_InvokesFromStringAndCachesModuleIfModuleIsNotCached() { // Arrange HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act await testSubject. InvokeFromStringAsync(() => _dummyExportsMultipleFunctionsModule, DUMMY_CACHE_IDENTIFIER, "incrementNumber").ConfigureAwait(false); // Assert // Ensure module was cached (bool success, int result) = await testSubject.TryInvokeFromCacheAsync <int>(DUMMY_CACHE_IDENTIFIER, "getNumber").ConfigureAwait(false); Assert.True(success); Assert.Equal(1, result); }
public async void InvokeFromStringAsync_WithTypeParameter_WithModuleFactory_InvokesFromStringAndCachesModuleIfModuleIsNotCached() { // Arrange const string dummyArg = "success"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act // Module hasn't been cached, so if this returns the expected value, string was sent over DummyResult result1 = await testSubject. InvokeFromStringAsync <DummyResult>(() => _dummyReturnsArgModule, DUMMY_CACHE_IDENTIFIER, args : new[] { dummyArg }).ConfigureAwait(false); // Assert // Ensure module was cached (bool success, DummyResult result2) = await testSubject.TryInvokeFromCacheAsync <DummyResult>(DUMMY_CACHE_IDENTIFIER, args : new[] { dummyArg }).ConfigureAwait(false); Assert.Equal(dummyArg, result1.Result); Assert.True(success); Assert.Equal(dummyArg, result2.Result); }
public async void AllInvokeMethods_ReceiveAndLogStderrOutput(string dummyLogArgument, string expectedResult) { // Arrange var resultStringBuilder = new StringBuilder(); HttpNodeJSService testSubject = CreateHttpNodeJSService(resultStringBuilder); // Act await testSubject. InvokeFromStringAsync <string>($@"module.exports = (callback) => {{ console.error({dummyLogArgument}); callback(); }}").ConfigureAwait(false); Thread.Sleep(1000); ((IDisposable)_serviceProvider).Dispose(); string result = resultStringBuilder.ToString(); // Assert Assert.Equal($"{nameof(LogLevel.Error)}: {expectedResult}\n", result, ignoreLineEndingDifferences: true); }
public async void AllInvokeMethods_ReceiveAndLogMessages() { // Arrange const string dummySinglelineString = "dummySingleLineString"; const string dummyMultilineString = "dummy\nMultiline\nString\n"; var resultStringBuilder = new StringBuilder(); HttpNodeJSService testSubject = CreateHttpNodeService(resultStringBuilder); // Act await testSubject. InvokeFromStringAsync <string>($"module.exports = (callback) => {{ console.log('{dummySinglelineString}'); console.log(`{dummyMultilineString}`); callback();}}").ConfigureAwait(false); // Disposing of HttpNodeServices causes Process.WaitForExit(500) to be called on the node process, this give it time to flush its output. // This isn't super clean. ((IDisposable)_serviceProvider).Dispose(); string result = resultStringBuilder.ToString(); // Assert Assert.Equal($"{dummySinglelineString}\n{dummyMultilineString}", result, ignoreLineEndingDifferences: true); }
public async void TryInvokeFromCacheAsync_InvokesJavascriptIfModuleIsCached() { // Arrange const string dummyResultString = "success"; const string dummyCacheIdentifier = "dummyCacheIdentifier"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Cache await testSubject. InvokeFromStringAsync <DummyResult>("module.exports = (callback, resultString) => callback(null, {result: resultString});", dummyCacheIdentifier, args : new[] { dummyResultString }). ConfigureAwait(false); // Act (bool success, DummyResult value) = await testSubject.TryInvokeFromCacheAsync <DummyResult>(dummyCacheIdentifier, args : new[] { dummyResultString }).ConfigureAwait(false); // Assert Assert.True(success); Assert.Equal(dummyResultString, value.Result); }
public async void TryInvokeFromCacheAsync_IsThreadSafe() { // Arrange const string dummyResultString = "success"; const string dummyCacheIdentifier = "dummyCacheIdentifier"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Cache await testSubject. InvokeFromStringAsync <DummyResult>("module.exports = (callback, resultString) => callback(null, {result: resultString});", dummyCacheIdentifier, args : new[] { dummyResultString }). ConfigureAwait(false); // Act var results = new ConcurrentQueue <(bool, DummyResult)>(); const int numThreads = 5; var threads = new List <Thread>(); for (int i = 0; i < numThreads; i++) { var thread = new Thread(() => results.Enqueue(testSubject.TryInvokeFromCacheAsync <DummyResult>(dummyCacheIdentifier, args: new[] { dummyResultString }).GetAwaiter().GetResult())); threads.Add(thread); thread.Start(); } foreach (Thread thread in threads) { thread.Join(); } // Assert Assert.Equal(numThreads, results.Count); foreach ((bool success, DummyResult value) in results) { Assert.True(success); Assert.Equal(dummyResultString, value.Result); } }
public async void AllInvokeMethods_ReceiveAndLogMessages() { // Arrange const string dummySinglelineString = "dummySingleLineString"; const string dummyMultilineString = "dummy\nMultiline\nString\n"; var resultStringBuilder = new StringBuilder(); HttpNodeJSService testSubject = CreateHttpNodeJSService(resultStringBuilder); // Act await testSubject. InvokeFromStringAsync <string>($@"module.exports = (callback) => {{ console.log('{dummySinglelineString}'); console.log(`{dummyMultilineString}`); callback(); // Does not work // process.stdout.end(); // process.on(finish, () => callback()); // Does not work // process.stdout.write('', 'utf8', () => callback()); }}").ConfigureAwait(false); // Disposing of HttpNodeServices causes Process.Kill and Process.WaitForExit(500) to be called on the node process, this gives it time for it to flush its output. // // TODO On Linux and macOS, Node.js does not flush stdout completely when Process.Kill is called, even if Process.WaitForExit is called immediately after. // Note that console.log just writes to stdout under the hood -https://nodejs.org/docs/latest-v8.x/api/console.html#console_console_log_data_args. // There flakiness causes this test to fail randomly. The whole stdout flushing issue seems like a persistent Node.js problem - https://github.com/nodejs/node/issues/6456. // Several attempts have been made to flush/write to stdout synchronously in the js above, to no avail. // The following Thread.Sleep(500) works almost all the time, but isn't a clean solution. Thread.Sleep(500); ((IDisposable)_serviceProvider).Dispose(); string result = resultStringBuilder.ToString(); // Assert Assert.Equal($"{nameof(LogLevel.Information)}: {dummySinglelineString}\n{nameof(LogLevel.Information)}: {dummyMultilineString}", result, ignoreLineEndingDifferences: true); }
public async void AllInvokeMethods_HandleHttpClientErrorsSuchAsMalformedRequests() { // Arrange HttpNodeJSService testSubject = CreateHttpNodeJSService(); await testSubject.InvokeFromStringAsync("module.exports = callback => callback();").ConfigureAwait(false); // Starts the Node.js process Uri dummyEndpoint = testSubject.Endpoint; var dummyJsonService = new JsonService(); // Act using (var dummyHttpClient = new HttpClient()) // Send a request with an invalid HTTP method. NodeJS drops the connection halfway through and fires the clientError event - https://nodejs.org/api/http.html#http_event_clienterror using (var dummyHttpRequestMessage = new HttpRequestMessage(new HttpMethod("INVALID"), dummyEndpoint)) using (HttpResponseMessage dummyHttpResponseMessage = await dummyHttpClient.SendAsync(dummyHttpRequestMessage).ConfigureAwait(false)) using (Stream dummyStream = await dummyHttpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false)) { InvocationError result = await dummyJsonService.DeserializeAsync <InvocationError>(dummyStream).ConfigureAwait(false); // Assert Assert.Equal(HttpStatusCode.InternalServerError, dummyHttpResponseMessage.StatusCode); Assert.False(string.IsNullOrWhiteSpace(result.ErrorMessage)); Assert.False(string.IsNullOrWhiteSpace(result.ErrorStack)); } }
public async void AllInvokeMethods_ThrowInvocationExceptionIfInvokedAsyncMethodThrowsError() { // Arrange const string dummyErrorString = "error"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act InvocationException result = await Assert.ThrowsAsync <InvocationException>(() => testSubject.InvokeFromStringAsync <DummyResult>("module.exports = async (errorString) => {throw new Error(errorString);}", args: new[] { dummyErrorString })). ConfigureAwait(false); // Assert Assert.StartsWith(dummyErrorString, result.Message); // Complete message includes the stack }
public async void AllInvokeMethods_ThrowInvocationExceptionIfNoExportNameSpecifiedAndModuleExportsIsNotAFunction() { // Arrange HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act InvocationException result = await Assert.ThrowsAsync <InvocationException>(() => testSubject.InvokeFromStringAsync <DummyResult>("module.exports = {result: 'not a function'};")). ConfigureAwait(false); // Assert Assert.StartsWith("The module \"module.exports = {result:...\" does not export a function.", result.Message); }
public async void AllInvokeMethods_ThrowInvocationExceptionIfModuleExportWithSpecifiedExportNameIsNotAFunction() { // Arrange const string dummyExportName = "dummyExportName"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act InvocationException result = await Assert.ThrowsAsync <InvocationException>(() => testSubject.InvokeFromStringAsync <DummyResult>($"module.exports = {{{dummyExportName}: 'not a function'}};", exportName: dummyExportName)). ConfigureAwait(false); // Assert Assert.StartsWith($"The export named {dummyExportName} from module \"module.exports = {{dummyEx...\" is not a function.", result.Message); }
public async void AllInvokeMethods_ThrowInvocationExceptionIfThereIsNoModuleExportWithSpecifiedExportName() { // Arrange const string dummyExportName = "dummyExportName"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act InvocationException result = await Assert.ThrowsAsync <InvocationException>(() => testSubject.InvokeFromStringAsync <DummyResult>("module.exports = (callback) => callback(null, {result: 'success'});", DUMMY_CACHE_IDENTIFIER, dummyExportName)). ConfigureAwait(false); // Assert Assert.StartsWith($"The module {DUMMY_CACHE_IDENTIFIER} has no export named {dummyExportName}.", result.Message); }
public async void AllInvokeMethods_ThrowInvocationExceptionIfModuleHasNoExports() { // Arrange const string dummyModule = "return null;"; HttpNodeJSService testSubject = CreateHttpNodeJSService(); // Act InvocationException result = await Assert.ThrowsAsync <InvocationException>(() => testSubject.InvokeFromStringAsync <DummyResult>(dummyModule)). ConfigureAwait(false); // Assert Assert.StartsWith($"The module \"{dummyModule}...\" has no exports. Ensure that the module assigns a function or an object containing functions to module.exports.", result.Message); }