public void CanSanitizeDotNetInteropExceptions() { // Arrange var expectedMessage = "An error ocurred while invoking '[Assembly]::Method'. Swapping to 'Development' environment will " + "display more detailed information about the error that occurred."; string GetMessage(DotNetInvocationInfo info) => $"An error ocurred while invoking '[{info.AssemblyName}]::{info.MethodIdentifier}'. Swapping to 'Development' environment will " + "display more detailed information about the error that occurred."; var runtime = new TestJSRuntime() { OnDotNetException = (invocationInfo) => new JSError { Message = GetMessage(invocationInfo) } }; var exception = new Exception("Some really sensitive data in here"); var invocation = new DotNetInvocationInfo("Assembly", "Method", 0, "0"); var result = new DotNetInvocationResult(exception, default); // Act runtime.EndInvokeDotNet(invocation, result); // Assert var call = runtime.EndInvokeDotNetCalls.Single(); Assert.Equal("0", call.CallId); Assert.False(call.Success); var jsError = Assert.IsType <JSError>(call.ResultOrError); Assert.Equal(expectedMessage, jsError.Message); }
public void CanCompleteAsyncCallsAsFailure() { // Arrange var runtime = new TestJSRuntime(); // Act/Assert: Tasks not initially completed var unrelatedTask = runtime.InvokeAsync <string>("unrelated call", Array.Empty <object>()); var task = runtime.InvokeAsync <string>("test identifier", Array.Empty <object>()); Assert.False(unrelatedTask.IsCompleted); Assert.False(task.IsCompleted); var bytes = Encoding.UTF8.GetBytes("\"This is a test exception\""); var reader = new Utf8JsonReader(bytes); reader.Read(); // Act/Assert: Task can be failed runtime.EndInvokeJS( runtime.BeginInvokeCalls[1].AsyncHandle, /* succeeded: */ false, ref reader); Assert.False(unrelatedTask.IsCompleted); Assert.True(task.IsCompleted); var exception = Assert.IsType <AggregateException>(task.AsTask().Exception); var jsException = Assert.IsType <JSException>(exception.InnerException); Assert.Equal("This is a test exception", jsException.Message); }
public Task CanCompleteAsyncCallsWithErrorsDuringDeserialization() { // Arrange var runtime = new TestJSRuntime(); // Act/Assert: Tasks not initially completed var unrelatedTask = runtime.InvokeAsync <string>("unrelated call", Array.Empty <object>()); var task = runtime.InvokeAsync <int>("test identifier", Array.Empty <object>()); Assert.False(unrelatedTask.IsCompleted); Assert.False(task.IsCompleted); var bytes = Encoding.UTF8.GetBytes("Not a string"); var reader = new Utf8JsonReader(bytes); // Act/Assert: Task can be failed runtime.EndInvokeJS( runtime.BeginInvokeCalls[1].AsyncHandle, /* succeeded: */ true, ref reader); Assert.False(unrelatedTask.IsCompleted); return(AssertTask()); async Task AssertTask() { var jsException = await Assert.ThrowsAsync <JSException>(async() => await task); Assert.IsAssignableFrom <JsonException>(jsException.InnerException); } }
public void ReceiveByteArray_AddsMultipleByteArrays() { // Arrange var runtime = new TestJSRuntime(); var byteArrays = new byte[10][]; for (var i = 0; i < 10; i++) { var byteArray = new byte[3]; Random.Shared.NextBytes(byteArray); byteArrays[i] = byteArray; } // Act for (var i = 0; i < 10; i++) { runtime.ReceiveByteArray(i, byteArrays[i]); } // Assert Assert.Equal(10, runtime.ByteArraysToBeRevived.Count); for (var i = 0; i < 10; i++) { Assert.Equal(byteArrays[i], runtime.ByteArraysToBeRevived.Buffer[i]); } }
public static async Task WithJSRuntime(Action <JSRuntimeBase> testCode) { // Since the tests rely on the asynclocal JSRuntime.Current, ensure we // are on a distinct async context with a non-null JSRuntime.Current await Task.Yield(); var runtime = new TestJSRuntime(); JSRuntime.SetCurrentJSRuntime(runtime); testCode(runtime); }
public void BeginTransmittingStream_MultipleStreams() { // Arrange var runtime = new TestJSRuntime(); var streamRef = new DotNetStreamReference(new MemoryStream()); // Act & Assert for (var i = 1; i <= 10; i++) { Assert.Equal(i, runtime.BeginTransmittingStream(streamRef)); } }
public async void ReadJSDataAsStreamAsync_ThrowsNotSupportedException() { // Arrange var runtime = new TestJSRuntime(); var dataReference = new JSStreamReference(runtime, 10, 10); // Act var exception = await Assert.ThrowsAsync <NotSupportedException>(async() => await runtime.ReadJSDataAsStreamAsync(dataReference, 10, CancellationToken.None)); // Assert Assert.Equal("The current JavaScript runtime does not support reading data streams.", exception.Message); }
public async Task InvokeAsync_CancelsAsyncTask_AfterDefaultTimeout() { // Arrange var runtime = new TestJSRuntime(); runtime.DefaultTimeout = TimeSpan.FromSeconds(1); // Act var task = runtime.InvokeAsync <object>("test identifier 1", "arg1", 123, true); // Assert await Assert.ThrowsAsync <TaskCanceledException>(async() => await task); }
public void TrackObjectReference_AllowsMultipleCallsUsingTheSameJSRuntime() { // Arrange var jsRuntime = new TestJSRuntime(); var objRef = DotNetObjectReference.Create(new object()); // Act var objectId1 = jsRuntime.TrackObjectReference(objRef); var objectId2 = jsRuntime.TrackObjectReference(objRef); // Act Assert.Equal(objectId1, objectId2); }
public void TrackObjectReference_AssignsObjectId() { // Arrange var jsRuntime = new TestJSRuntime(); var objRef = DotNetObjectReference.Create(new object()); // Act var objectId = jsRuntime.TrackObjectReference(objRef); // Act Assert.Equal(objectId, objRef.ObjectId); Assert.Equal(1, objRef.ObjectId); }
public async Task InvokeAsync_CancelsAsyncTasksWhenCancellationTokenFires() { // Arrange using var cts = new CancellationTokenSource(); var runtime = new TestJSRuntime(); // Act var task = runtime.InvokeAsync <object>("test identifier 1", cts.Token, new object[] { "arg1", 123, true }); cts.Cancel(); // Assert await Assert.ThrowsAsync <TaskCanceledException>(async() => await task); }
public void ReceiveByteArray_AddsInitialByteArray() { // Arrange var runtime = new TestJSRuntime(); var byteArray = new byte[] { 1, 5, 7 }; // Act runtime.ReceiveByteArray(0, byteArray); // Assert Assert.Equal(1, runtime.ByteArraysToBeRevived.Count); Assert.Equal(byteArray, runtime.ByteArraysToBeRevived.Buffer[0]); }
public void InvokeAsync_CompletesSuccessfullyBeforeTimeout() { // Arrange var runtime = new TestJSRuntime(); runtime.DefaultTimeout = TimeSpan.FromSeconds(10); var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes("null")); // Act var task = runtime.InvokeAsync <object>("test identifier 1", "arg1", 123, true); runtime.EndInvokeJS(2, succeeded: true, ref reader); Assert.True(task.IsCompletedSuccessfully); }
public void DoubleDispose_Works() { // Arrange var objRef = DotNetObjectReference.Create("Hello world"); var jsRuntime = new TestJSRuntime(); jsRuntime.TrackObjectReference(objRef); var objectId = objRef.ObjectId; // Act Assert.Same(objRef, jsRuntime.GetObjectReference(objectId)); objRef.Dispose(); // Assert objRef.Dispose(); // If we got this far, this did not throw. }
public void TrackObjectReference_ThrowsIfDifferentJSRuntimeInstancesAreUsed() { // Arrange var objRef = DotNetObjectReference.Create("Hello world"); var expected = $"{objRef.GetType().Name} is already being tracked by a different instance of {nameof(JSRuntime)}. A common cause is caching an instance of {nameof(DotNetObjectReference<string>)}" + $" globally. Consider creating instances of {nameof(DotNetObjectReference<string>)} at the JSInterop callsite."; var jsRuntime1 = new TestJSRuntime(); var jsRuntime2 = new TestJSRuntime(); jsRuntime1.TrackObjectReference(objRef); // Act var ex = Assert.Throws <InvalidOperationException>(() => jsRuntime2.TrackObjectReference(objRef)); // Assert Assert.Equal(expected, ex.Message); }
public async Task InvokeAsync_DoesNotStartWorkWhenCancellationHasBeenRequested() { // Arrange using var cts = new CancellationTokenSource(); cts.Cancel(); var runtime = new TestJSRuntime(); // Act var task = runtime.InvokeAsync <object>("test identifier 1", cts.Token, new object[] { "arg1", 123, true }); cts.Cancel(); // Assert await Assert.ThrowsAsync <TaskCanceledException>(async() => await task); Assert.Empty(runtime.BeginInvokeCalls); }
public void ReceiveByteArray_ThrowsExceptionIfUnexpectedId() { // Arrange var runtime = new TestJSRuntime(); runtime.ByteArraysToBeRevived.Append(new byte[] { 1, 5, 7 }); runtime.ByteArraysToBeRevived.Append(new byte[] { 3, 10, 15 }); var byteArray = new byte[] { 1, 5, 7 }; // Act var ex = Assert.Throws <ArgumentOutOfRangeException>(() => runtime.ReceiveByteArray(7, byteArray)); // Assert Assert.Equal(2, runtime.ByteArraysToBeRevived.Count); Assert.Equal("Element id '7' cannot be added to the byte arrays to be revived with length '2'.", ex.Message); }
public void ReceiveByteArray_ClearsByteArraysToBeRevivedWhenIdIsZero() { // Arrange var runtime = new TestJSRuntime(); runtime.ByteArraysToBeRevived.Append(new byte[] { 1, 5, 7 }); runtime.ByteArraysToBeRevived.Append(new byte[] { 3, 10, 15 }); var byteArray = new byte[] { 1, 5, 7 }; // Act runtime.ReceiveByteArray(0, byteArray); // Assert Assert.Equal(1, runtime.ByteArraysToBeRevived.Count); Assert.Equal(byteArray, runtime.ByteArraysToBeRevived.Buffer[0]); }
public void Dispose_StopsTrackingObject() { // Arrange var objRef = DotNetObjectReference.Create("Hello world"); var jsRuntime = new TestJSRuntime(); jsRuntime.TrackObjectReference(objRef); var objectId = objRef.ObjectId; var expected = $"There is no tracked object with id '{objectId}'. Perhaps the DotNetObjectReference instance was already disposed."; // Act Assert.Same(objRef, jsRuntime.GetObjectReference(objectId)); objRef.Dispose(); // Assert Assert.True(objRef.Disposed); Assert.Throws <ArgumentException>(() => jsRuntime.GetObjectReference(objectId)); }
public void CanCompleteAsyncCallsWithComplexType() { // Arrange var runtime = new TestJSRuntime(); var task = runtime.InvokeAsync <TestPoco>("test identifier", Array.Empty <object>()); var bytes = Encoding.UTF8.GetBytes("{\"id\":10, \"name\": \"Test\"}"); var reader = new Utf8JsonReader(bytes); // Act/Assert: Task can be completed runtime.EndInvokeJS( runtime.BeginInvokeCalls[0].AsyncHandle, /* succeeded: */ true, ref reader); Assert.True(task.IsCompleted); var poco = task.Result; Assert.Equal(10, poco.Id); Assert.Equal("Test", poco.Name); }
public void CanSanitizeDotNetInteropExceptions() { // Arrange var runtime = new TestJSRuntime(); var exception = new Exception("Some really sensitive data in here"); var invocation = new DotNetInvocationInfo("TestAssembly", "TestMethod", 0, "0"); var result = new DotNetInvocationResult(exception, default); // Act runtime.EndInvokeDotNet(invocation, result); // Assert var call = runtime.EndInvokeDotNetCalls.Single(); Assert.Equal("0", call.CallId); Assert.False(call.Success); var error = Assert.IsType <JSError>(call.ResultError); Assert.Same(exception, error.InnerException); Assert.Equal(invocation, error.InvocationInfo); }
public void DispatchesAsyncCallsWithDistinctAsyncHandles() { // Arrange var runtime = new TestJSRuntime(); // Act runtime.InvokeAsync <object>("test identifier 1", "arg1", 123, true); runtime.InvokeAsync <object>("test identifier 2", "some other arg"); // Assert Assert.Collection(runtime.BeginInvokeCalls, call => { Assert.Equal("test identifier 1", call.Identifier); Assert.Equal("[\"arg1\",123,true]", call.ArgsJson); }, call => { Assert.Equal("test identifier 2", call.Identifier); Assert.Equal("[\"some other arg\"]", call.ArgsJson); Assert.NotEqual(runtime.BeginInvokeCalls[0].AsyncHandle, call.AsyncHandle); }); }
public void CanCompleteAsyncCallsAsSuccess() { // Arrange var runtime = new TestJSRuntime(); // Act/Assert: Tasks not initially completed var unrelatedTask = runtime.InvokeAsync <string>("unrelated call", Array.Empty <object>()); var task = runtime.InvokeAsync <string>("test identifier", Array.Empty <object>()); Assert.False(unrelatedTask.IsCompleted); Assert.False(task.IsCompleted); var bytes = Encoding.UTF8.GetBytes("\"my result\""); var reader = new Utf8JsonReader(bytes); // Act/Assert: Task can be completed runtime.EndInvokeJS( runtime.BeginInvokeCalls[1].AsyncHandle, /* succeeded: */ true, ref reader); Assert.False(unrelatedTask.IsCompleted); Assert.True(task.IsCompleted); Assert.Equal("my result", task.Result); }
public Task CompletingSameAsyncCallMoreThanOnce_IgnoresSecondResultAsync() { // Arrange var runtime = new TestJSRuntime(); // Act/Assert var task = runtime.InvokeAsync <string>("test identifier", Array.Empty <object>()); var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle; var firstReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Some data\"")); var secondReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Exception\"")); runtime.EndInvokeJS(asyncHandle, true, ref firstReader); runtime.EndInvokeJS(asyncHandle, false, ref secondReader); return(AssertTask()); async Task AssertTask() { var result = await task; Assert.Equal("Some data", result); } }
public void SerializesDotNetObjectWrappersInKnownFormat() { // Arrange var runtime = new TestJSRuntime(); var obj1 = new object(); var obj2 = new object(); var obj3 = new object(); // Act // Showing we can pass the DotNetObject either as top-level args or nested var obj1Ref = DotNetObjectReference.Create(obj1); var obj1DifferentRef = DotNetObjectReference.Create(obj1); runtime.InvokeAsync <object>("test identifier", obj1Ref, new Dictionary <string, object> { { "obj2", DotNetObjectReference.Create(obj2) }, { "obj3", DotNetObjectReference.Create(obj3) }, { "obj1SameRef", obj1Ref }, { "obj1DifferentRef", obj1DifferentRef }, }); // Assert: Serialized as expected var call = runtime.BeginInvokeCalls.Single(); Assert.Equal("test identifier", call.Identifier); Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":2},\"obj3\":{\"__dotNetObject\":3},\"obj1SameRef\":{\"__dotNetObject\":1},\"obj1DifferentRef\":{\"__dotNetObject\":4}}]", call.ArgsJson); // Assert: Objects were tracked Assert.Same(obj1Ref, runtime.GetObjectReference(1)); Assert.Same(obj1, obj1Ref.Value); Assert.NotSame(obj1Ref, runtime.GetObjectReference(2)); Assert.Same(obj2, runtime.GetObjectReference(2).Value); Assert.Same(obj3, runtime.GetObjectReference(3).Value); Assert.Same(obj1, runtime.GetObjectReference(4).Value); }