Skip to content
This repository was archived by the owner on Oct 11, 2023. It is now read-only.

Commit b8dab03

Browse files
maple10001hathind-ms
authored andcommitted
Add device model scripts endpoint (#231)
* Add device model scripts endpoint
1 parent 4462cd4 commit b8dab03

28 files changed

+1477
-110
lines changed
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
5+
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
6+
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
7+
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
8+
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter;
9+
using Moq;
10+
using Newtonsoft.Json;
11+
using Services.Test.helpers;
12+
using Xunit;
13+
14+
namespace Services.Test
15+
{
16+
public class DeviceModelScriptsTest
17+
{
18+
private const string STORAGE_COLLECTION = "deviceModelScripts";
19+
private readonly Mock<IStorageAdapterClient> storage;
20+
private readonly Mock<ILogger> logger;
21+
private readonly DeviceModelScripts target;
22+
23+
public DeviceModelScriptsTest()
24+
{
25+
this.storage = new Mock<IStorageAdapterClient>();
26+
this.logger = new Mock<ILogger>();
27+
28+
this.target = new DeviceModelScripts(
29+
this.storage.Object,
30+
this.logger.Object);
31+
}
32+
33+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
34+
public void InitialListIsEmpty()
35+
{
36+
// Arrange
37+
this.ThereAreNoDeviceModelScriptsInStorage();
38+
39+
// Act
40+
var result = this.target.GetListAsync().Result;
41+
42+
// Assert
43+
Assert.Empty(result);
44+
}
45+
46+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
47+
public void ItCreatesDeviceModelScriptInStorage()
48+
{
49+
// Arrange
50+
var id = Guid.NewGuid().ToString();
51+
var eTag = Guid.NewGuid().ToString();
52+
var deviceModelScript = new DeviceModelScript { Id = id, ETag = eTag };
53+
54+
this.storage
55+
.Setup(x => x.UpdateAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
56+
.ReturnsAsync(this.BuildValueApiModel(deviceModelScript));
57+
58+
// Act
59+
DeviceModelScript result = this.target.InsertAsync(deviceModelScript).Result;
60+
61+
// Assert
62+
Assert.NotNull(result);
63+
Assert.Equal(deviceModelScript.Id, result.Id);
64+
Assert.Equal(deviceModelScript.ETag, result.ETag);
65+
66+
this.storage.Verify(
67+
x => x.UpdateAsync(STORAGE_COLLECTION, deviceModelScript.Id, It.IsAny<string>(), null), Times.Once());
68+
}
69+
70+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
71+
public void DeviceModelScriptsCanBeUpserted()
72+
{
73+
// Arrange
74+
var id = Guid.NewGuid().ToString();
75+
76+
var deviceModelScript = new DeviceModelScript { Id = id, ETag = "oldEtag" };
77+
this.TheScriptExists(id, deviceModelScript);
78+
79+
var updatedSimulationScript = new DeviceModelScript { Id = id, ETag = "newETag" };
80+
this.storage
81+
.Setup(x => x.UpdateAsync(
82+
STORAGE_COLLECTION,
83+
It.IsAny<string>(),
84+
It.IsAny<string>(),
85+
It.IsAny<string>()))
86+
.ReturnsAsync(this.BuildValueApiModel(updatedSimulationScript));
87+
88+
// Act
89+
this.target.UpsertAsync(deviceModelScript)
90+
.Wait(Constants.TEST_TIMEOUT);
91+
92+
// Assert
93+
this.storage.Verify(x => x.GetAsync(STORAGE_COLLECTION, id), Times.Once);
94+
this.storage.Verify(x => x.UpdateAsync(
95+
STORAGE_COLLECTION,
96+
id,
97+
It.Is<string>(json => JsonConvert.DeserializeObject<DeviceModelScript>(json).Id == id && !json.Contains("ETag")),
98+
"oldEtag"), Times.Once());
99+
100+
Assert.Equal(updatedSimulationScript.Id, deviceModelScript.Id);
101+
// The call to UpsertAsync() modifies the object, so the ETags will match
102+
Assert.Equal(updatedSimulationScript.ETag, deviceModelScript.ETag);
103+
}
104+
105+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
106+
public void ItCreatesDeviceModelScriptWhenSimulationScriptNotFoundInUpserting()
107+
{
108+
// Arrange
109+
var id = Guid.NewGuid().ToString();
110+
var deviceModelScript = new DeviceModelScript { Id = id, ETag = "Etag" };
111+
this.TheScriptDoesntExist(id);
112+
this.storage
113+
.Setup(x => x.UpdateAsync(
114+
STORAGE_COLLECTION,
115+
It.IsAny<string>(),
116+
It.IsAny<string>(),
117+
It.IsAny<string>()))
118+
.ReturnsAsync(this.BuildValueApiModel(deviceModelScript));
119+
120+
// Act
121+
this.target.UpsertAsync(deviceModelScript).Wait(TimeSpan.FromSeconds(30));
122+
123+
// Assert - the app uses PUT with given ID
124+
this.storage.Verify(x => x.UpdateAsync(
125+
STORAGE_COLLECTION,
126+
id,
127+
It.IsAny<string>(),
128+
null));
129+
}
130+
131+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
132+
public void ItThrowsConflictingResourceExceptionIfEtagDoesNotMatchInUpserting()
133+
{
134+
// Arrange
135+
var id = Guid.NewGuid().ToString();
136+
var deviceModelScriptInStorage = new DeviceModelScript { Id = id, ETag = "ETag" };
137+
this.TheScriptExists(id, deviceModelScriptInStorage);
138+
139+
// Act & Assert
140+
var deviceModelScript = new DeviceModelScript { Id = id, ETag = "not-matching-Etag" };
141+
Assert.ThrowsAsync<ConflictingResourceException>(
142+
async () => await this.target.UpsertAsync(deviceModelScript))
143+
.Wait(Constants.TEST_TIMEOUT);
144+
}
145+
146+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
147+
public void ItThrowsExceptionWhenInsertDeviceModelScriptFailed()
148+
{
149+
// Arrange
150+
var deviceModelScript = new DeviceModelScript { Id = "id", ETag = "Etag" };
151+
this.storage
152+
.Setup(x => x.UpdateAsync(
153+
It.IsAny<string>(),
154+
It.IsAny<string>(),
155+
It.IsAny<string>(),
156+
It.IsAny<string>()))
157+
.ThrowsAsync(new SomeException());
158+
159+
// Act & Assert
160+
Assert.ThrowsAsync<ExternalDependencyException>(
161+
async () => await this.target.InsertAsync(deviceModelScript))
162+
.Wait(Constants.TEST_TIMEOUT);
163+
}
164+
165+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
166+
public void ItFailsToUpsertWhenUnableToFetchScriptFromStorage()
167+
{
168+
// Arrange
169+
var deviceModelScript = new DeviceModelScript { Id = "id", ETag = "Etag" };
170+
this.storage
171+
.Setup(x => x.GetAsync(It.IsAny<string>(), It.IsAny<string>()))
172+
.ThrowsAsync(new SomeException());
173+
174+
// Act & Assert
175+
Assert.ThrowsAsync<ExternalDependencyException>(
176+
async () => await this.target.UpsertAsync(deviceModelScript))
177+
.Wait(Constants.TEST_TIMEOUT);
178+
}
179+
180+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
181+
public void ItFailsToUpsertWhenStorageUpdateFails()
182+
{
183+
// Arrange
184+
var id = Guid.NewGuid().ToString();
185+
var deviceModelScript = new DeviceModelScript { Id = id, ETag = "Etag" };
186+
this.TheScriptExists(id, deviceModelScript);
187+
188+
this.storage
189+
.Setup(x => x.UpdateAsync(
190+
It.IsAny<string>(),
191+
It.IsAny<string>(),
192+
It.IsAny<string>(),
193+
It.IsAny<string>()))
194+
.ThrowsAsync(new SomeException());
195+
196+
// Act & Assert
197+
Assert.ThrowsAsync<ExternalDependencyException>(
198+
async () => await this.target.UpsertAsync(deviceModelScript))
199+
.Wait(Constants.TEST_TIMEOUT);
200+
}
201+
202+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
203+
public void ItThrowsExternalDependencyExceptionWhenFailedFetchingDeviceModelScriptInStorage()
204+
{
205+
// Arrange
206+
var deviceModelScript = new DeviceModelScript { Id = "id", ETag = "Etag" };
207+
208+
// Act
209+
var ex = Record.Exception(() => this.target.UpsertAsync(deviceModelScript).Result);
210+
211+
// Assert
212+
Assert.IsType<ExternalDependencyException>(ex.InnerException);
213+
}
214+
215+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
216+
public void ItThrowsExceptionWhenDeleteDeviceModelScriptFailed()
217+
{
218+
// Arrange
219+
this.storage
220+
.Setup(x => x.DeleteAsync(
221+
It.IsAny<string>(),
222+
It.IsAny<string>()))
223+
.ThrowsAsync(new SomeException());
224+
225+
// Act & Assert
226+
Assert.ThrowsAsync<ExternalDependencyException>(
227+
async () => await this.target.DeleteAsync("someId"))
228+
.Wait(Constants.TEST_TIMEOUT);
229+
}
230+
231+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
232+
public void ItFailsToGetDeviceModelScriptsWhenStorageFails()
233+
{
234+
// Arrange
235+
this.storage
236+
.Setup(x => x.GetAsync(It.IsAny<string>(), It.IsAny<string>()))
237+
.ThrowsAsync(new SomeException());
238+
239+
// Act & Assert
240+
Assert.ThrowsAsync<ExternalDependencyException>(
241+
async () => await this.target.GetAsync("someId"))
242+
.Wait(Constants.TEST_TIMEOUT);
243+
}
244+
245+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
246+
public void ItThrowsExceptionWhenGetListOfDeviceModelScriptsDeserializeFailed()
247+
{
248+
// Arrange
249+
this.SetupAListOfInvalidDeviceModelScriptsInStorage();
250+
251+
// Act & Assert
252+
Assert.ThrowsAsync<ExternalDependencyException>(
253+
async () => await this.target.GetListAsync())
254+
.Wait(Constants.TEST_TIMEOUT);
255+
}
256+
257+
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
258+
public void ItThrowsExceptionWhenGetDeviceModelScriptByInvalidId()
259+
{
260+
// Act & Assert
261+
Assert.ThrowsAsync<InvalidInputException>(
262+
async () => await this.target.GetAsync(string.Empty))
263+
.Wait(Constants.TEST_TIMEOUT);
264+
}
265+
266+
private void SetupAListOfInvalidDeviceModelScriptsInStorage()
267+
{
268+
var list = new ValueListApiModel();
269+
var value = new ValueApiModel
270+
{
271+
Key = "key",
272+
Data = "{ 'invalid': json",
273+
ETag = "etag"
274+
};
275+
list.Items.Add(value);
276+
277+
this.storage
278+
.Setup(x => x.GetAllAsync(It.IsAny<string>()))
279+
.ReturnsAsync(list);
280+
}
281+
282+
private void TheScriptDoesntExist(string id)
283+
{
284+
this.storage
285+
.Setup(x => x.GetAsync(STORAGE_COLLECTION, id))
286+
.Throws<ResourceNotFoundException>();
287+
}
288+
289+
private void TheScriptExists(string id, DeviceModelScript deviceModelScript)
290+
{
291+
this.storage
292+
.Setup(x => x.GetAsync(STORAGE_COLLECTION, id))
293+
.ReturnsAsync(this.BuildValueApiModel(deviceModelScript));
294+
}
295+
296+
private ValueApiModel BuildValueApiModel(DeviceModelScript deviceModelScript)
297+
{
298+
return new ValueApiModel
299+
{
300+
Key = deviceModelScript.Id,
301+
Data = JsonConvert.SerializeObject(deviceModelScript),
302+
ETag = deviceModelScript.ETag
303+
};
304+
}
305+
306+
private void ThereAreNoDeviceModelScriptsInStorage()
307+
{
308+
this.storage
309+
.Setup(x => x.GetAllAsync(STORAGE_COLLECTION))
310+
.ReturnsAsync(new ValueListApiModel());
311+
}
312+
}
313+
}

Services.Test/Simulation/JavascriptInterpreterTest.cs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System;
44
using System.Collections.Generic;
5+
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
56
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
67
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
78
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
@@ -21,6 +22,7 @@ public class JavascriptInterpreterTest
2122
private readonly Mock<IServicesConfig> config;
2223
private readonly Mock<ILogger> logger;
2324
private readonly Mock<ISmartDictionary> properties;
25+
private readonly Mock<IDeviceModelScripts> simulationScripts;
2426
private readonly JavascriptInterpreter target;
2527

2628
public JavascriptInterpreterTest(ITestOutputHelper log)
@@ -31,19 +33,25 @@ public JavascriptInterpreterTest(ITestOutputHelper log)
3133
this.config.SetupGet(x => x.DeviceModelsFolder).Returns("./data/devicemodels/");
3234
this.config.SetupGet(x => x.DeviceModelsScriptsFolder).Returns("./data/devicemodels/scripts/");
3335
this.properties = new Mock<ISmartDictionary>();
34-
36+
this.simulationScripts = new Mock<IDeviceModelScripts>();
3537
this.logger = new Mock<ILogger>();
3638

37-
this.target = new JavascriptInterpreter(this.config.Object, this.logger.Object);
39+
this.target = new JavascriptInterpreter(
40+
this.simulationScripts.Object,
41+
this.config.Object,
42+
this.logger.Object);
3843
}
3944

4045
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
4146
public void ReturnedStateIsIntact()
4247
{
4348
// Arrange
4449
SmartDictionary deviceState = new SmartDictionary();
45-
46-
var filename = "chiller-01-state.js";
50+
51+
var script = new Script
52+
{
53+
Path = "chiller-01-state.js"
54+
};
4755
var context = new Dictionary<string, object>
4856
{
4957
["currentTime"] = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:sszzz"),
@@ -64,7 +72,7 @@ public void ReturnedStateIsIntact()
6472
deviceState.SetAll(state);
6573

6674
// Act
67-
this.target.Invoke(filename, context, deviceState, this.properties.Object);
75+
this.target.Invoke(script, context, deviceState, this.properties.Object);
6876

6977
// Assert
7078
Assert.Equal(state.Count, deviceState.GetAll().Count);
@@ -83,18 +91,18 @@ public void TestJavascriptFiles()
8391
// Arrange
8492
SmartDictionary deviceState = new SmartDictionary();
8593

86-
var files = new List<string>
94+
var files = new List<Script>
8795
{
88-
"chiller-01-state.js",
89-
"chiller-02-state.js",
90-
"elevator-01-state.js",
91-
"elevator-02-state.js",
92-
"engine-01-state.js",
93-
"engine-02-state.js",
94-
"prototype-01-state.js",
95-
"prototype-02-state.js",
96-
"truck-01-state.js",
97-
"truck-02-state.js"
96+
new Script { Path = "chiller-01-state.js" },
97+
new Script { Path = "chiller-02-state.js" },
98+
new Script { Path = "elevator-01-state.js" },
99+
new Script { Path = "elevator-02-state.js" },
100+
new Script { Path = "engine-01-state.js" },
101+
new Script { Path = "engine-02-state.js" },
102+
new Script { Path = "prototype-01-state.js" },
103+
new Script { Path = "prototype-02-state.js" },
104+
new Script { Path = "truck-01-state.js" },
105+
new Script { Path = "truck-02-state.js" }
98106
};
99107
var context = new Dictionary<string, object>
100108
{

0 commit comments

Comments
 (0)