Skip to content

Commit 96b9cac

Browse files
authored
test: Added unit tests for BrowserMonitoringStreamInjector (#1611)
1 parent be8f76e commit 96b9cac

File tree

3 files changed

+207
-19
lines changed

3 files changed

+207
-19
lines changed

src/Agent/NewRelic/Agent/Core/BrowserMonitoring/BrowserMonitoringStreamInjector.cs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ public class BrowserMonitoringStreamInjector : Stream
1818

1919
private Action<byte[], int, int> _streamWriter;
2020

21-
public BrowserMonitoringStreamInjector(Func<string> getJavascriptAgentScript, Stream output, Encoding contentEncoding)
21+
public BrowserMonitoringStreamInjector(Func<string> getJavascriptAgentScript, Stream output, Encoding contentEncoding, BrowserMonitoringWriter browserMonitoringWriter = null)
2222
{
23-
_jsWriter = new BrowserMonitoringWriter(getJavascriptAgentScript);
23+
_jsWriter = browserMonitoringWriter ?? new BrowserMonitoringWriter(getJavascriptAgentScript);
2424
OutputStream = output;
2525
_contentEncoding = contentEncoding;
2626
}
@@ -69,26 +69,11 @@ public override void Write(byte[] buffer, int offset, int count)
6969
// BEWARE: There is no try/catch between this method and the users application! Anything that can throw *must* be wrapped in a try/catch block! We cannot wrap this in a try/catch block because we should not catch exceptions thrown by the underlying stream.
7070

7171
// the first time Write is called, get the function that we will use to write
72-
if (_streamWriter == null)
73-
_streamWriter = GetStreamWriter();
72+
_streamWriter ??= GetInjectingStreamWriter(_contentEncoding);
7473

7574
_streamWriter(buffer, offset, count);
7675
}
7776

78-
private Action<byte[], int, int> GetStreamWriter()
79-
{
80-
try
81-
{
82-
return GetInjectingStreamWriter(_contentEncoding);
83-
}
84-
catch (Exception exception)
85-
{
86-
// logged at debug level since the exception is likely caused by the user setting the content-type to something invalid or the wrapper provided functions failing
87-
try { Log.Debug(exception); } catch { }
88-
return PassThroughStreamWriter;
89-
}
90-
}
91-
9277
private void PassThroughStreamWriter(byte[] buffer, int offset, int count)
9378
{
9479
OutputStream.Write(buffer, offset, count);

src/Agent/NewRelic/Agent/Core/BrowserMonitoring/BrowserMonitoringWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private string AttemptInsertionPriorToBodyTag(string content)
5050
}
5151

5252
// Specification for Javascript insertion: https://newrelic.atlassian.net/wiki/spaces/eng/pages/50299103/BAM+Agent+Auto-Instrumentation
53-
public string WriteScriptHeaders(string content)
53+
public virtual string WriteScriptHeaders(string content)
5454
{
5555
var openingHeadTagIndex = FindFirstOpeningHeadTag(content);
5656

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System;
5+
using System.IO;
6+
using System.Text;
7+
using NUnit.Framework;
8+
using Telerik.JustMock;
9+
10+
namespace NewRelic.Agent.Core.BrowserMonitoring
11+
{
12+
[TestFixture]
13+
public class BrowserMonitoringStreamInjectorTests
14+
{
15+
private const string SampleJavaScript = "<script>console.log('test');</script>";
16+
17+
[Test]
18+
public void Constructor_SetsProperties()
19+
{
20+
var baseStream = Mock.Create<Stream>();
21+
var contentEncoding = Encoding.UTF8;
22+
Func<string> getJavascriptAgentScript = () => SampleJavaScript;
23+
24+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding);
25+
26+
Assert.AreEqual(baseStream.CanRead, injector.CanRead);
27+
Assert.AreEqual(baseStream.CanSeek, injector.CanSeek);
28+
Assert.AreEqual(baseStream.CanWrite, injector.CanWrite);
29+
Assert.AreEqual(baseStream.Length, injector.Length);
30+
}
31+
32+
[Test]
33+
public void Position_GetAndSet()
34+
{
35+
var baseStream = Mock.Create<Stream>();
36+
long position = 123;
37+
Mock.Arrange(() => baseStream.Position).Returns(() => position);
38+
Mock.ArrangeSet(() => baseStream.Position = Arg.IsAny<long>()).DoInstead<long>(value => position = value);
39+
40+
var contentEncoding = Encoding.UTF8;
41+
Func<string> getJavascriptAgentScript = () => SampleJavaScript;
42+
43+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding);
44+
45+
Assert.AreEqual(123, injector.Position);
46+
injector.Position = 456;
47+
Assert.AreEqual(456, baseStream.Position);
48+
}
49+
50+
[Test]
51+
public void Close_ClosesBaseStream()
52+
{
53+
var baseStream = Mock.Create<Stream>();
54+
Mock.Arrange(() => baseStream.Close());
55+
var contentEncoding = Encoding.UTF8;
56+
Func<string> getJavascriptAgentScript = () => SampleJavaScript;
57+
58+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding);
59+
injector.Close();
60+
61+
Mock.Assert(() => baseStream.Close(), Occurs.Once());
62+
}
63+
64+
[Test]
65+
public void Flush_CallsBaseStreamFlush()
66+
{
67+
var baseStream = Mock.Create<Stream>();
68+
Mock.Arrange(() => baseStream.Flush());
69+
var contentEncoding = Encoding.UTF8;
70+
Func<string> getJavascriptAgentScript = () => SampleJavaScript;
71+
72+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding);
73+
injector.Flush();
74+
75+
Mock.Assert(() => baseStream.Flush(), Occurs.Once());
76+
}
77+
78+
[Test]
79+
public void Seek_CallsBaseStreamSeek()
80+
{
81+
var baseStream = Mock.Create<Stream>();
82+
Mock.Arrange(() => baseStream.Seek(123, SeekOrigin.Begin)).Returns(456);
83+
var contentEncoding = Encoding.UTF8;
84+
Func<string> getJavascriptAgentScript = () => SampleJavaScript;
85+
86+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding);
87+
var result = injector.Seek(123, SeekOrigin.Begin);
88+
89+
Assert.AreEqual(456, result);
90+
Mock.Assert(() => baseStream.Seek(123, SeekOrigin.Begin), Occurs.Once());
91+
}
92+
93+
[Test]
94+
public void SetLength_CallsBaseStreamSetLength()
95+
{
96+
var baseStream = Mock.Create<Stream>();
97+
Mock.Arrange(() => baseStream.SetLength(123));
98+
var contentEncoding = Encoding.UTF8;
99+
Func<string> getJavascriptAgentScript = () => SampleJavaScript;
100+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding);
101+
injector.SetLength(123);
102+
103+
Mock.Assert(() => baseStream.SetLength(123), Occurs.Once());
104+
}
105+
106+
[Test]
107+
public void Read_CallsBaseStreamRead()
108+
{
109+
var baseStream = Mock.Create<Stream>();
110+
byte[] buffer = new byte[100];
111+
Mock.Arrange(() => baseStream.Read(buffer, 10, 50)).Returns(30);
112+
var contentEncoding = Encoding.UTF8;
113+
Func<string> getJavascriptAgentScript = () => SampleJavaScript;
114+
115+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding);
116+
var result = injector.Read(buffer, 10, 50);
117+
118+
Assert.AreEqual(30, result);
119+
Mock.Assert(() => baseStream.Read(buffer, 10, 50), Occurs.Once());
120+
}
121+
122+
[Test]
123+
public void Write_CallsBaseStreamWrite()
124+
{
125+
var baseStream = Mock.Create<Stream>();
126+
byte[] buffer = new byte[100];
127+
Mock.Arrange(() => baseStream.Write(buffer, 10, 50));
128+
var contentEncoding = Encoding.UTF8;
129+
Func<string> getJavascriptAgentScript = () => SampleJavaScript;
130+
131+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding);
132+
injector.Write(buffer, 10, 50);
133+
134+
Mock.Assert(() => baseStream.Write(buffer, 10, 50), Occurs.Once());
135+
}
136+
137+
[Test]
138+
public void Write_InjectsRUMScript()
139+
{
140+
var baseStream = new MemoryStream();
141+
var body = "<body><h1>Hello World!</h1></body>";
142+
byte[] buffer = Encoding.UTF8.GetBytes("<html><head></head>" + body + "</html>");
143+
var contentEncoding = Encoding.UTF8;
144+
Func<string> getJavascriptAgentScript = () => SampleJavaScript;
145+
146+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding);
147+
injector.Write(buffer, 0, buffer.Length);
148+
injector.Flush();
149+
150+
baseStream.Position = 0;
151+
using (StreamReader reader = new StreamReader(baseStream, contentEncoding))
152+
{
153+
string content = reader.ReadToEnd();
154+
Assert.IsTrue(content.Contains(SampleJavaScript), "JavaScript was not injected.");
155+
Assert.IsTrue(content.Contains(body), "body was not written without modification.");
156+
}
157+
}
158+
159+
[Test]
160+
public void Write_WritesContentWithoutModification_IfUnableToInjectRUM()
161+
{
162+
var baseStream = new MemoryStream();
163+
byte[] buffer = Encoding.UTF8.GetBytes("Sample content");
164+
var contentEncoding = Encoding.UTF8;
165+
Func<string> getJavascriptAgentScript = () => null;
166+
167+
// if WriteScriptHeaders() returns null, RUM isn't injected.
168+
var browserMonitoringWriter = Mock.Create<BrowserMonitoringWriter>();
169+
Mock.Arrange(() => browserMonitoringWriter.WriteScriptHeaders(Arg.IsAny<string>())).Returns((string)null);
170+
171+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding, browserMonitoringWriter);
172+
injector.Write(buffer, 0, buffer.Length);
173+
injector.Flush();
174+
175+
baseStream.Position = 0;
176+
using (StreamReader reader = new StreamReader(baseStream, contentEncoding))
177+
{
178+
string content = reader.ReadToEnd();
179+
Assert.AreEqual(Encoding.UTF8.GetString(buffer), content, "Content should be written without modification.");
180+
}
181+
}
182+
183+
[Test]
184+
public void TryGetInjectedBytes_ReturnsNullWhenDecodedBufferIsEmpty()
185+
{
186+
var baseStream = new MemoryStream();
187+
byte[] buffer = new byte[0]; // Empty buffer
188+
var contentEncoding = Encoding.UTF8;
189+
Func<string> getJavascriptAgentScript = () => SampleJavaScript;
190+
191+
var injector = new BrowserMonitoringStreamInjector(getJavascriptAgentScript, baseStream, contentEncoding);
192+
injector.Write(buffer, 0, buffer.Length);
193+
injector.Flush();
194+
195+
baseStream.Position = 0;
196+
using (StreamReader reader = new StreamReader(baseStream, contentEncoding))
197+
{
198+
string content = reader.ReadToEnd();
199+
Assert.IsEmpty(content, "Content should be empty.");
200+
}
201+
}
202+
}
203+
}

0 commit comments

Comments
 (0)