public async void TryInvokeFromCacheAsync_WithoutTypeParameter_IsThreadSafe()
        {
            // Arrange
            HttpNodeJSService testSubject = CreateHttpNodeJSService();
            await testSubject.InvokeFromStringAsync(_dummyExportsMultipleFunctionsModule, DUMMY_CACHE_IDENTIFIER, "incrementNumber").ConfigureAwait(false);

            // Act
            const int numThreads = 5;
            var       threads    = new List <Thread>();

            for (int i = 0; i < numThreads; i++)
            {
                var thread = new Thread(() => testSubject.TryInvokeFromCacheAsync(DUMMY_CACHE_IDENTIFIER, "incrementNumber").GetAwaiter().GetResult());
                threads.Add(thread);
                thread.Start();
            }
            foreach (Thread thread in threads)
            {
                thread.Join();
            }

            // Assert
            int result = await testSubject.InvokeFromStringAsync <int>(_dummyExportsMultipleFunctionsModule, DUMMY_CACHE_IDENTIFIER, "getNumber").ConfigureAwait(false);

            Assert.Equal(numThreads + 1, result);
        }
        public void InvokeFromStreamAsync_WithTypeParameter_WithRawStreamModule_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(() =>
                {
                    MemoryStream memoryStream = CreateMemoryStream(_dummyReturnsArgModule);
                    results.Enqueue(testSubject.InvokeFromStreamAsync <DummyResult>(memoryStream, 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_WithTypeParameter_WithModuleFactory_IsThreadSafe()
        {
            // Arrange
            HttpNodeJSService testSubject = CreateHttpNodeJSService();

            // Act
            var       results    = new ConcurrentQueue <int>();
            const int numThreads = 5;
            var       threads    = new List <Thread>();

            for (int i = 0; i < numThreads; i++)
            {
                var thread = new Thread(() => results.Enqueue(testSubject.
                                                              InvokeFromStringAsync <int>(_dummyExportsMultipleFunctionsModule, DUMMY_CACHE_IDENTIFIER, "incrementAndGetNumber").GetAwaiter().GetResult()));
                threads.Add(thread);
                thread.Start();
            }
            foreach (Thread thread in threads)
            {
                thread.Join();
            }

            // Assert
            Assert.Equal(numThreads, results.Count);
            // Module shouldn't get cached more than once, we should get exactly [1,2,3,4,5]
            List <int> resultsList = results.ToList();

            resultsList.Sort();
            for (int i = 0; i < numThreads; i++)
            {
                Assert.Equal(resultsList[i], i + 1);
            }
        }
        public async void InvokeFromFileAsync_WithoutTypeParameter_IsThreadSafe()
        {
            // Arrange
            HttpNodeJSService testSubject = CreateHttpNodeJSService(projectPath: _projectPath);

            // Act
            const int numThreads = 5;
            var       threads    = new List <Thread>();

            for (int i = 0; i < numThreads; i++)
            {
                var thread = new Thread(() => testSubject.
                                        InvokeFromFileAsync(DUMMY_EXPORTS_MULTIPLE_FUNCTIONS_MODULE_FILE, "incrementNumber").GetAwaiter().GetResult());
                threads.Add(thread);
                thread.Start();
            }
            foreach (Thread thread in threads)
            {
                thread.Join();
            }

            // Assert
            int result = await testSubject.InvokeFromFileAsync <int>(DUMMY_EXPORTS_MULTIPLE_FUNCTIONS_MODULE_FILE, "getNumber").ConfigureAwait(false);

            Assert.Equal(numThreads, result);
        }
        public async void InMemoryInvokeMethods_LoadRequiredModulesFromNodeModulesInProjectDirectory()
        {
            // Arrange
            const string      dummyCode   = @"public string ExampleFunction(string arg)
{
    // Example comment
    return arg + ""dummyString"";
}";
            HttpNodeJSService testSubject = CreateHttpNodeJSService(projectPath: _projectPath);

            // Act
            string result = await testSubject.InvokeFromStringAsync <string>(@"const prismjs = require('prismjs');
require('prismjs/components/prism-csharp');

module.exports = (callback, code) => {
    var result = prismjs.highlight(code, prismjs.languages.csharp, 'csharp');

    callback(null, result);
};", args : new[] { dummyCode }).ConfigureAwait(false);

            // Assert
            const string expectedResult = @"<span class=""token keyword"">public</span> <span class=""token keyword"">string</span> <span class=""token function"">ExampleFunction</span><span class=""token punctuation"">(</span><span class=""token keyword"">string</span> arg<span class=""token punctuation"">)</span>
<span class=""token punctuation"">{</span>
    <span class=""token comment"">// Example comment</span>
    <span class=""token keyword"">return</span> arg <span class=""token operator"">+</span> <span class=""token string"">""dummyString""</span><span class=""token punctuation"">;</span>
<span class=""token punctuation"">}</span>";

            Assert.Equal(expectedResult, result);
        }
        public async void AllInvokeMethods_ReceiveAndLogStdoutOutput(string dummyLogArgument, string expectedResult)
        {
            // Arrange
            var resultStringBuilder       = new StringBuilder();
            HttpNodeJSService testSubject = CreateHttpNodeJSService(resultStringBuilder);

            // Act
            await testSubject.
            InvokeFromStringAsync <string>($@"module.exports = (callback) => {{ 
            console.log({dummyLogArgument}); 
            callback();

            // Does not work
            // process.stdout.end();
            // process.on(finish, () => callback());

            // Does not work
            // process.stdout.write('', 'utf8', () => callback());
        }}").ConfigureAwait(false);

            Thread.Sleep(1000);
            ((IDisposable)_serviceProvider).Dispose();
            string result = resultStringBuilder.ToString();

            // Assert
            Assert.Equal($"{nameof(LogLevel.Information)}: {expectedResult}\n", result, ignoreLineEndingDifferences: true);
        }
        public void InvokeFromFileAsync_IsThreadSafe()
        {
            // Arrange
            const string      dummyModule       = "dummyModule.js";
            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.InvokeFromFileAsync <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 InvokeFromFileAsync_WithTypeParameter_IsThreadSafe()
        {
            // Arrange
            const string      dummyArg    = "success";
            HttpNodeJSService testSubject = CreateHttpNodeJSService(projectPath: _projectPath);

            // 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.
                                                              InvokeFromFileAsync <DummyResult>(DUMMY_RETURNS_ARG_MODULE_FILE, 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 async void TryInvokeFromCacheAsync_WithTypeParameter_IsThreadSafe()
        {
            // Arrange
            const string      dummyArg    = "success";
            HttpNodeJSService testSubject = CreateHttpNodeJSService();
            await testSubject.InvokeFromStringAsync <DummyResult>(_dummyReturnsArgModule, DUMMY_CACHE_IDENTIFIER, args : new[] { dummyArg }).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>(DUMMY_CACHE_IDENTIFIER, args: new[] { dummyArg }).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(dummyArg, value.Result);
            }
        }
        public async void TryInvokeFromCacheAsync_WithoutTypeParameter_ReturnsFalseIfModuleIsNotCached()
        {
            // Arrange
            HttpNodeJSService testSubject = CreateHttpNodeJSService();

            // Act
            bool success = await testSubject.TryInvokeFromCacheAsync(DUMMY_CACHE_IDENTIFIER).ConfigureAwait(false);

            // Assert
            Assert.False(success);
        }
        public async void InvokeFromFileAsync_WithTypeParameter_InvokesFromFile()
        {
            const string      dummyArg    = "success";
            HttpNodeJSService testSubject = CreateHttpNodeJSService(projectPath: _projectPath);

            // Act
            DummyResult result = await testSubject.
                                 InvokeFromFileAsync <DummyResult>(DUMMY_RETURNS_ARG_MODULE_FILE, args : new[] { dummyArg }).ConfigureAwait(false);

            // Assert
            Assert.Equal(dummyArg, result.Result);
        }
        public async void InvokeFromFileAsync_InvokesJavascript()
        {
            const string      dummyResultString = "success";
            HttpNodeJSService testSubject       = CreateHttpNodeJSService();

            // Act
            DummyResult result = await testSubject.
                                 InvokeFromFileAsync <DummyResult>("dummyModule.js", args : new[] { dummyResultString }).ConfigureAwait(false);

            // Assert
            Assert.Equal(dummyResultString, result.Result);
        }
Example #13
0
        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 TryInvokeFromCacheAsync_WithTypeParameter_ReturnsFalseIfModuleIsNotCached()
        {
            // Arrange
            HttpNodeJSService testSubject = CreateHttpNodeJSService();

            // Act
            (bool success, DummyResult value) = await testSubject.TryInvokeFromCacheAsync <DummyResult>(DUMMY_CACHE_IDENTIFIER, args : new[] { "success" }).ConfigureAwait(false);

            // Assert
            Assert.False(success);
            Assert.Null(value);
        }
        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 InvokeFromStreamAsync_WithTypeParameter_WithRawStreamModule_InvokesFromStream()
        {
            // Arrange
            const string      dummyArg     = "success";
            HttpNodeJSService testSubject  = CreateHttpNodeJSService();
            MemoryStream      memoryStream = CreateMemoryStream(_dummyReturnsArgModule);

            // Act
            DummyResult result = await testSubject.InvokeFromStreamAsync <DummyResult>(memoryStream, args : new[] { dummyArg }).ConfigureAwait(false);

            // Assert
            Assert.Equal(dummyArg, result.Result);
        }
        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 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 InvokeFromStringAsync_WithoutTypeParameter_WithRawStringModule_InvokesFromString()
        {
            const string      dummyArg    = "success";
            HttpNodeJSService testSubject = CreateHttpNodeJSService(projectPath: _projectPath);

            // Act
            await testSubject.InvokeFromStringAsync(_dummyExportsMultipleFunctionsModule, DUMMY_CACHE_IDENTIFIER, "setString", new[] { dummyArg }).ConfigureAwait(false);

            // Assert
            DummyResult result = await testSubject.
                                 InvokeFromStringAsync <DummyResult>(_dummyExportsMultipleFunctionsModule, DUMMY_CACHE_IDENTIFIER, "getString").ConfigureAwait(false);

            Assert.Equal(dummyArg, result.Result);
        }
        public async void InvokeFromStringAsync_WithTypeParameter_WithModuleFactory_InvokesFromCacheIfModuleIsCached()
        {
            // Arrange
            const string      dummyArg    = "success";
            HttpNodeJSService testSubject = CreateHttpNodeJSService();
            await testSubject.InvokeFromStringAsync(_dummyExportsMultipleFunctionsModule, DUMMY_CACHE_IDENTIFIER, "setString", new[] { dummyArg }).ConfigureAwait(false);

            // Act
            DummyResult result = await testSubject.
                                 InvokeFromStringAsync <DummyResult>(() => null, DUMMY_CACHE_IDENTIFIER, "getString").ConfigureAwait(false);

            // Assert
            Assert.Equal(dummyArg, result.Result);
        }
        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_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);
        }
        public async void InvokeFromFileAsync_WithoutTypeParameter_InvokesFromFile()
        {
            const string      dummyArg    = "success";
            HttpNodeJSService testSubject = CreateHttpNodeJSService(projectPath: _projectPath);

            // Act
            await testSubject.InvokeFromFileAsync(DUMMY_EXPORTS_MULTIPLE_FUNCTIONS_MODULE_FILE, "setString", new[] { dummyArg }).ConfigureAwait(false);

            // Assert
            DummyResult result = await testSubject.
                                 InvokeFromFileAsync <DummyResult>(DUMMY_EXPORTS_MULTIPLE_FUNCTIONS_MODULE_FILE, "getString").ConfigureAwait(false);

            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 TryInvokeFromCacheAsync_ReturnsFalseIfModuleIsNotCached()
        {
            // Arrange
            const string      dummyResultString    = "success";
            const string      dummyCacheIdentifier = "dummyCacheIdentifier";
            HttpNodeJSService testSubject          = CreateHttpNodeJSService();

            // Act
            (bool success, DummyResult value) = await testSubject.TryInvokeFromCacheAsync <DummyResult>(dummyCacheIdentifier, args : new[] { dummyResultString }).ConfigureAwait(false);

            // Assert
            Assert.False(success);
            Assert.Null(value);
        }
        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 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 TryInvokeFromCacheAsync_WithoutTypeParameter_InvokesFromCacheIfModuleIsCached()
        {
            // Arrange
            HttpNodeJSService testSubject = CreateHttpNodeJSService();
            await testSubject.InvokeFromStringAsync(_dummyExportsMultipleFunctionsModule, DUMMY_CACHE_IDENTIFIER, "incrementNumber").ConfigureAwait(false);

            // Act
            bool success = await testSubject.TryInvokeFromCacheAsync(DUMMY_CACHE_IDENTIFIER, "incrementNumber").ConfigureAwait(false);

            // Assert
            Assert.True(success);
            int result = await testSubject.InvokeFromStringAsync <int>(_dummyExportsMultipleFunctionsModule, DUMMY_CACHE_IDENTIFIER, "getNumber").ConfigureAwait(false);

            Assert.Equal(2, 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
        }