/// <summary> /// Handles the ItemCommand event of the rLocations control. /// </summary> /// <param name="source">The source of the event.</param> /// <param name="e">The <see cref="System.Web.UI.WebControls.RepeaterCommandEventArgs"/> instance containing the event data.</param> protected void rLocations_ItemCommand(object source, System.Web.UI.WebControls.RepeaterCommandEventArgs e) { int?locationId = (e.CommandArgument as string).AsIntegerOrNull(); if (locationId.HasValue) { var rockContext = new RockContext(); var location = new LocationService(rockContext).Get(locationId.Value); if (location != null) { if (e.CommandName == "Open" && !location.IsActive) { location.IsActive = true; rockContext.SaveChanges(); KioskDevice.Clear(); } else if (e.CommandName == "Close" && location.IsActive) { location.IsActive = false; rockContext.SaveChanges(); KioskDevice.Clear(); } } BindManagerLocationsGrid(); } }
public void TestKioskPurchaseTicket() { FakeDeviceClient fakeDeviceClient = new FakeDeviceClient(); FakeEventScheduler fakeScheduler = new FakeEventScheduler(); TestContext.WriteLine(string.Empty); TestContext.WriteLine(">> Testing the purchase ticket simulated event.."); KioskDevice device = new KioskDevice(deviceconfig, fakeDeviceClient, fakeScheduler); // execute a purchase ticket event and check the result, it should always be false Assert.IsFalse(fakeScheduler.EventList[0].EventDelegate()); // that delegate should have sent one message to the cloud Assert.AreEqual(fakeDeviceClient.sendMessageLog.Count, 1); // check the message sent to make sure its correct // create a sample request for comparison PurchaseTicketRequest expectedRequest = new PurchaseTicketRequest() { DeviceId = deviceconfig.DeviceId, DeviceType = deviceconfig.DeviceType, TransactionId = "fakeId", CreateTime = System.DateTime.UtcNow, Price = 1, MethodName = "ReceivePurchaseTicketResponse" }; // get request message into an object so we can compare it PurchaseTicketRequest actualRequest = JsonConvert.DeserializeObject <PurchaseTicketRequest>(fakeDeviceClient.sendMessageLog[0]); // compare properties to make sure they're valid. Assert.AreEqual(actualRequest.DeviceId, expectedRequest.DeviceId); Assert.AreEqual(actualRequest.DeviceType, expectedRequest.DeviceType); Assert.AreEqual(actualRequest.MessageType, expectedRequest.MessageType); // skipping the TransactionID and CreationTime Assert.IsTrue(actualRequest.Price > 2); Assert.IsTrue(actualRequest.Price < 100); Assert.AreEqual(actualRequest.MethodName, expectedRequest.MethodName); // /// test the CloudToEvent PurchaseResponse call we expect back // TestContext.WriteLine(string.Empty); TestContext.WriteLine(">> Testing the ticket approval direct method.."); // test the direct method itself PurchaseTicketPayload approvePurchaseMethodkRequest = new PurchaseTicketPayload() { IsApproved = true, DeviceId = expectedRequest.DeviceId, DeviceType = expectedRequest.DeviceType, MessageType = expectedRequest.MessageType, }; string requestString = JsonConvert.SerializeObject(approvePurchaseMethodkRequest); // execute the method MethodRequest methodRequest = new MethodRequest(expectedRequest.MethodName, Encoding.UTF8.GetBytes(requestString)); MethodResponse methodresult = fakeDeviceClient.directMethods[0](methodRequest, null).Result; // check results Assert.AreEqual(methodresult.Status, 200); // got back an ok }
public void TestPurchaseDenied() { FakeDeviceClient fakeDeviceClient = new FakeDeviceClient(); FakeEventScheduler fakeScheduler = new FakeEventScheduler(); TestContext.WriteLine(">> Testing the Kiosk Device's Low Stock notification.."); PurchaseTicketPayload approvePurchaseMethodkRequest = new PurchaseTicketPayload() { DeviceId = deviceconfig.DeviceId, DeviceType = deviceconfig.DeviceType, MessageType = "Purchase", IsApproved = false }; // create our test device KioskDevice device = new KioskDevice(deviceconfig, fakeDeviceClient, fakeScheduler); TestContext.WriteLine(string.Empty); TestContext.WriteLine(">> Purchasing tickets, shouldn't throw event"); string requestString = JsonConvert.SerializeObject(approvePurchaseMethodkRequest); MethodRequest methodRequest = new MethodRequest("ReceivePurchaseTicketResponse", Encoding.UTF8.GetBytes(requestString)); MethodResponse myresult = fakeDeviceClient.directMethods[0](methodRequest, null).Result; // no ticket was issued, count remained the same and no message sent to cloud Assert.AreEqual(device.CurrentStockLevel, deviceconfig.InitialStockCount); Assert.AreEqual(fakeDeviceClient.sendMessageLog.Count, 0); }
/// <summary> /// Handles the ItemCommand event of the rLocations control. /// </summary> /// <param name="source">The source of the event.</param> /// <param name="e">The <see cref="System.Web.UI.WebControls.RepeaterCommandEventArgs"/> instance containing the event data.</param> protected void rLocations_ItemCommand(object source, System.Web.UI.WebControls.RepeaterCommandEventArgs e) { int?locationId = (e.CommandArgument as string).AsIntegerOrNull(); if (locationId.HasValue) { var rockContext = new RockContext(); var location = new LocationService(rockContext).Get(locationId.Value); if (location != null) { if (e.CommandName == "Open" && !location.IsActive) { location.IsActive = true; rockContext.SaveChanges(); } else if (e.CommandName == "Close" && location.IsActive) { location.IsActive = false; rockContext.SaveChanges(); } // flush the current kiosk ( the kiosk only caches groups, etc for active locations, so we need to flush anytime a location is opened/closed ) if (this.CurrentKioskId.HasValue) { KioskDevice.Flush(this.CurrentKioskId.Value); } } BindManagerLocationsGrid(); } }
/// <summary> /// Handles the Click event of the btnSave control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> protected void btnSave_Click(object sender, EventArgs e) { using (var rockContext = new RockContext()) { GroupLocationService groupLocationService = new GroupLocationService(rockContext); ScheduleService scheduleService = new ScheduleService(rockContext); bool schedulesChanged = false; var gridViewRows = gGroupLocationSchedule.Rows; foreach (GridViewRow row in gridViewRows.OfType <GridViewRow>()) { int groupLocationId = int.Parse(gGroupLocationSchedule.DataKeys[row.RowIndex].Value as string); GroupLocation groupLocation = groupLocationService.Get(groupLocationId); if (groupLocation != null) { foreach (var fieldCell in row.Cells.OfType <DataControlFieldCell>()) { var checkBoxTemplateField = fieldCell.ContainingField as CheckBoxEditableField; if (checkBoxTemplateField != null) { CheckBox checkBox = fieldCell.Controls[0] as CheckBox; string dataField = (fieldCell.ContainingField as CheckBoxEditableField).DataField; int scheduleId = int.Parse(dataField.Replace("scheduleField_", string.Empty)); // update GroupLocationSchedule depending on if the Schedule is Checked or not if (checkBox.Checked) { // This schedule is selected, so if GroupLocationSchedule doesn't already have this schedule, add it if (!groupLocation.Schedules.Any(a => a.Id == scheduleId)) { var schedule = scheduleService.Get(scheduleId); groupLocation.Schedules.Add(schedule); schedulesChanged = true; } } else { // This schedule is not selected, so if GroupLocationSchedule has this schedule, delete it if (groupLocation.Schedules.Any(a => a.Id == scheduleId)) { groupLocation.Schedules.Remove(groupLocation.Schedules.FirstOrDefault(a => a.Id == scheduleId)); schedulesChanged = true; } } } } } } if (schedulesChanged) { rockContext.SaveChanges(); KioskDevice.Clear(); } } NavigateToHomePage(); }
/// <summary> /// Attempts to match a known kiosk based on the IP address of the client. /// </summary> private void GetKioskType(Kiosk kiosk, RockContext rockContext) { if (kiosk.KioskType != null) { DeviceService deviceService = new DeviceService(rockContext); //Load matching device and update or create information var device = deviceService.Queryable().Where(d => d.Name == kiosk.Name).FirstOrDefault(); //create new device to match our kiosk if (device == null) { device = new Device(); device.DeviceTypeValueId = DefinedValueCache.Get(Rock.SystemGuid.DefinedValue.DEVICE_TYPE_CHECKIN_KIOSK).Id; device.Name = kiosk.Name; deviceService.Add(device); } device.LoadAttributes(); device.IPAddress = kiosk.IPAddress; device.Locations.Clear(); foreach (var loc in kiosk.KioskType.Locations.ToList()) { device.Locations.Add(loc); } device.PrintFrom = kiosk.PrintFrom; device.PrintToOverride = kiosk.PrintToOverride; device.PrinterDeviceId = kiosk.PrinterDeviceId; rockContext.SaveChanges(); if (PageParameter("DateTime").AsDateTime().HasValue) { device.SetAttributeValue("core_device_DebugDateTime", PageParameter("datetime")); } else { device.SetAttributeValue("core_device_DebugDateTime", ""); } device.SaveAttributeValues(rockContext); CurrentKioskId = device.Id; CurrentGroupTypeIds = kiosk.KioskType.GroupTypes.Select(gt => gt.Id).ToList(); CurrentCheckinTypeId = kiosk.KioskType.CheckinTemplateId; CurrentCheckInState = null; CurrentWorkflow = null; Session["KioskTypeId"] = kiosk.KioskType.Id; Session["KioskMessage"] = kiosk.KioskType.Message; KioskDevice.Remove(device.Id); SaveState(); NavigateToNextPage(); } else { ltDNS.Text = kiosk.Name; pnlMain.Visible = true; } }
/// <summary> /// Printers the has cutter. /// </summary> /// <param name="labels">The labels.</param> /// <returns></returns> private static bool PrinterHasCutter(List <CheckInLabel> labels) { bool hasCutter = false; var deviceId = labels.Select(a => a.PrinterDeviceId).FirstOrDefault(); if (deviceId != null) { KioskDevice kioskDevice = KioskDevice.Get(deviceId.GetValueOrDefault(), new List <int>()); hasCutter = kioskDevice.Device.GetAttributeValue(Rock.SystemKey.DeviceAttributeKey.DEVICE_HAS_CUTTER).AsBoolean(); } return(hasCutter); }
public void TestBaseKioskDevice() { FakeDeviceClient fakeDeviceClient = new FakeDeviceClient(); FakeEventScheduler fakeScheduler = new FakeEventScheduler(); TestContext.WriteLine(string.Empty); TestContext.WriteLine(">> Testing the Kiosk Device's base functionality.."); KioskDevice device = new KioskDevice(deviceconfig, fakeDeviceClient, fakeScheduler); // should only have 1 scheduled event Assert.AreEqual(fakeScheduler.EventList.Count, 1, "Incorrect number of scheduled events"); // should only have 1 callback method Assert.AreEqual(fakeDeviceClient.directMethods.Count, 1, "Incorrect number of callback methods"); }
private Kiosk ConfigureKiosk() { var rockContext = new RockContext(); var kioskTypeId = ddlCampus.SelectedValue.AsInteger(); var kioskType = KioskTypeCache.Get(kioskTypeId); var kioskName = "Mobile:" + kioskType.Name.RemoveAllNonAlphaNumericCharacters(); var mobileUserCategory = CategoryCache.Get(org.secc.FamilyCheckin.Utilities.Constants.KIOSK_CATEGORY_MOBILEUSER); var kioskService = new KioskService(rockContext); var kiosk = kioskService.Queryable("KioskType") .Where(k => k.Name == kioskName) .FirstOrDefault(); if (kiosk == null) { kiosk = new Kiosk { Name = kioskName, CategoryId = mobileUserCategory.Id, Description = "Automatically created mobile Kiosk" }; kioskService.Add(kiosk); } kiosk.KioskTypeId = kioskType.Id; rockContext.SaveChanges(); DeviceService deviceService = new DeviceService(rockContext); //Load matching device and update or create information var device = deviceService.Queryable("Location").Where(d => d.Name == kioskName).FirstOrDefault(); var dirty = false; //create new device to match our kiosk if (device == null) { device = new Device(); device.DeviceTypeValueId = DefinedValueCache.Get(Rock.SystemGuid.DefinedValue.DEVICE_TYPE_CHECKIN_KIOSK).Id; device.Name = kioskName; deviceService.Add(device); device.PrintFrom = PrintFrom.Client; device.PrintToOverride = PrintTo.Default; dirty = true; } var deviceLocationIds = device.Locations.Select(l => l.Id); var ktLocationIds = kioskType.Locations.Select(l => l.Id); var unmatchedDeviceLocations = deviceLocationIds.Except(ktLocationIds).Any(); var unmatchedKtLocations = ktLocationIds.Except(deviceLocationIds).Any(); if (unmatchedDeviceLocations || unmatchedKtLocations) { LocationService locationService = new LocationService(rockContext); device.Locations.Clear(); foreach (var loc in kioskType.Locations.ToList()) { var location = locationService.Get(loc.Id); device.Locations.Add(location); } dirty = true; } if (this.IsUserAuthorized(Rock.Security.Authorization.ADMINISTRATE)) { device.LoadAttributes(); if (PageParameter("datetime").IsNotNullOrWhiteSpace()) { device.SetAttributeValue("core_device_DebugDateTime", PageParameter("datetime")); } else { device.SetAttributeValue("core_device_DebugDateTime", ""); } } if (dirty) { rockContext.SaveChanges(); device.SaveAttributeValues(rockContext); KioskDevice.Remove(device.Id); } LocalDeviceConfig.CurrentKioskId = device.Id; LocalDeviceConfig.CurrentGroupTypeIds = kiosk.KioskType.GroupTypes.Select(gt => gt.Id).ToList(); LocalDeviceConfig.CurrentCheckinTypeId = kiosk.KioskType.CheckinTemplateId; CurrentCheckInState = null; CurrentWorkflow = null; var kioskTypeCookie = this.Page.Request.Cookies["KioskTypeId"]; if (kioskTypeCookie == null) { kioskTypeCookie = new System.Web.HttpCookie("KioskTypeId"); } kioskTypeCookie.Expires = RockDateTime.Now.AddYears(1); kioskTypeCookie.Value = kiosk.KioskType.Id.ToString(); this.Page.Response.Cookies.Set(kioskTypeCookie); Session["KioskTypeId"] = kioskType.Id; SaveState(); return(kiosk); }
private void ActivateKiosk(Kiosk kiosk, bool logout) { RockContext rockContext = new RockContext(); DeviceService deviceService = new DeviceService(rockContext); KioskService kioskService = new KioskService(rockContext); //The kiosk can come in a variety of states. //Get a fresh version with our context to avoid context errors. kiosk = kioskService.Get(kiosk.Id); //Load matching device and update or create information var device = deviceService.Queryable().Where(d => d.Name == kiosk.Name).FirstOrDefault(); //create new device to match our kiosk if (device == null) { device = new Device(); device.DeviceTypeValueId = DefinedValueCache.Get(Rock.SystemGuid.DefinedValue.DEVICE_TYPE_CHECKIN_KIOSK).Id; device.Name = kiosk.Name; deviceService.Add(device); } device.LoadAttributes(); device.IPAddress = kiosk.IPAddress; device.Locations.Clear(); foreach (var loc in kiosk.KioskType.Locations.ToList()) { device.Locations.Add(loc); } device.PrintFrom = kiosk.PrintFrom; device.PrintToOverride = kiosk.PrintToOverride; device.PrinterDeviceId = kiosk.PrinterDeviceId; rockContext.SaveChanges(); if (PageParameter("DateTime").AsDateTime().HasValue) { device.SetAttributeValue("core_device_DebugDateTime", PageParameter("datetime")); } else { device.SetAttributeValue("core_device_DebugDateTime", ""); } device.SaveAttributeValues(rockContext); LocalDeviceConfig.CurrentKioskId = device.Id; LocalDeviceConfig.CurrentGroupTypeIds = kiosk.KioskType.GroupTypes.Select(gt => gt.Id).ToList(); LocalDeviceConfig.CurrentCheckinTypeId = kiosk.KioskType.CheckinTemplateId; CurrentCheckInState = null; CurrentWorkflow = null; var kioskTypeCookie = this.Page.Request.Cookies["KioskTypeId"]; if (kioskTypeCookie == null) { kioskTypeCookie = new System.Web.HttpCookie("KioskTypeId"); } kioskTypeCookie.Expires = RockDateTime.Now.AddYears(1); kioskTypeCookie.Value = kiosk.KioskType.Id.ToString(); this.Page.Response.Cookies.Set(kioskTypeCookie); Session["KioskTypeId"] = kiosk.KioskType.Id; Session["KioskMessage"] = kiosk.KioskType.Message; //Clean things up so we have the freshest possible version. KioskTypeCache.Remove(kiosk.KioskTypeId ?? 0); KioskDevice.Remove(device.Id); Dictionary <string, string> pageParameters = new Dictionary <string, string>(); if (kiosk.KioskType.Theme.IsNotNullOrWhiteSpace() && !GetAttributeValue("Manual").AsBoolean()) { LocalDeviceConfig.CurrentTheme = kiosk.KioskType.Theme; pageParameters.Add("theme", LocalDeviceConfig.CurrentTheme); } if (logout) { pageParameters.Add("logout", "true"); } SaveState(); NavigateToNextPage(pageParameters); }
public static void Clear(KioskDevice kioskDevice) { var groupTypeIds = kioskDevice.KioskGroupTypes.Select(gt => gt.GroupType.Id).ToList(); Clear(groupTypeIds); }
public PrintSessionLabelsResponse PrintSessionLabels([FromUri] string session, [FromUri] int?kioskId = null) { List <Guid?> sessionGuids; // // The session data is a comma separated list of Guid values. // try { sessionGuids = session.SplitDelimitedValues() .Select(a => { var guid = a.AsGuidOrNull(); // // Check if this is a standard Guid format. // if (guid.HasValue) { return(guid.Value); } return(GuidHelper.FromShortStringOrNull(a)); }) .ToList(); } catch { sessionGuids = null; } // // If no session guids were found or an error occurred trying to // parse them, then return an error. // if (sessionGuids == null || sessionGuids.Count == 0) { return(new PrintSessionLabelsResponse { Labels = new List <CheckInLabel>(), Messages = new List <string> { "No check-in sessions were specified." } }); } using (var rockContext = new RockContext()) { KioskDevice printer = null; // // If they specified a kiosk, attempt to load the printer for // that kiosk. // if (kioskId.HasValue && kioskId.Value != 0) { var kiosk = KioskDevice.Get(kioskId.Value, null); if ((kiosk?.Device?.PrinterDeviceId).HasValue) { // // We aren't technically loading a kiosk, but this lets us // load the printer IP address from cache rather than hitting // the database each time. // printer = KioskDevice.Get(kiosk.Device.PrinterDeviceId.Value, null); } } var attendanceService = new AttendanceService(rockContext); // // Retrieve all session label data from the database, then deserialize // it, then make one giant list of labels and finally order it. // var labels = attendanceService.Queryable() .AsNoTracking() .Where(a => sessionGuids.Contains(a.AttendanceCheckInSession.Guid)) .DistinctBy(a => a.AttendanceCheckInSessionId) .Select(a => a.AttendanceData.LabelData) .ToList() .Select(a => a.FromJsonOrNull <List <CheckInLabel> >()) .Where(a => a != null) .SelectMany(a => a) .OrderBy(a => a.PersonId) .ThenBy(a => a.Order) .ToList(); foreach (var label in labels) { // // If the label is being printed to the kiosk and client printing // is enabled and the client has specified their device // identifier then we need to update the label data to have the // new printer information. // if (label.PrintTo == PrintTo.Kiosk && label.PrintFrom == PrintFrom.Client && printer != null) { label.PrinterDeviceId = printer.Device.Id; label.PrinterAddress = printer.Device.IPAddress; } } var response = new PrintSessionLabelsResponse { Labels = labels.Where(l => l.PrintFrom == PrintFrom.Client).ToList() }; // // Update any labels to convert the relative URL path to an absolute URL // path for client printing. // if (response.Labels.Any()) { var urlRoot = Request.RequestUri.GetLeftPart(UriPartial.Authority); if (Request.Headers.Contains("X-Forwarded-Proto") && Request.Headers.Contains("X-Forwarded-Host")) { urlRoot = $"{Request.Headers.GetValues( "X-Forwarded-Proto" ).First()}://{Request.Headers.GetValues( "X-Forwarded-Host" ).First()}"; } #if DEBUG // This is extremely useful when debugging with ngrok and an iPad on the local network. // X-Original-Host will contain the name of your ngrok hostname, therefore the labels will // get a LabelFile url that will actually work with that iPad. if (Request.Headers.Contains("X-Forwarded-Proto") && Request.Headers.Contains("X-Original-Host")) { urlRoot = $"{Request.Headers.GetValues( "X-Forwarded-Proto" ).First()}://{Request.Headers.GetValues( "X-Original-Host" ).First()}"; } #endif response.Labels.ForEach(l => l.LabelFile = urlRoot + l.LabelFile); } var printFromServer = labels.Where(l => l.PrintFrom == PrintFrom.Server).ToList(); if (printFromServer.Any()) { var messages = ZebraPrint.PrintLabels(printFromServer); response.Messages = messages; } return(response); } }
public void TestLowStock() { FakeDeviceClient fakeDeviceClient = new FakeDeviceClient(); FakeEventScheduler fakeScheduler = new FakeEventScheduler(); TestContext.WriteLine(">> Testing the Kiosk Device's Low Stock notification.."); PurchaseTicketPayload approvePurchaseMethodkRequest = new PurchaseTicketPayload() { DeviceId = deviceconfig.DeviceId, DeviceType = deviceconfig.DeviceType, MessageType = MessageType.cmdPurchaseTicket, IsApproved = true }; // create our test device KioskDevice device = new KioskDevice(deviceconfig, fakeDeviceClient, fakeScheduler); device.InitializeAsync().Wait(); TestContext.WriteLine(string.Empty); TestContext.WriteLine(">> Purchasing tickets, shouldn't throw event"); string requestString = JsonConvert.SerializeObject(approvePurchaseMethodkRequest); MethodRequest methodRequest = new MethodRequest("ReceivePurchaseTicketResponse", Encoding.UTF8.GetBytes(requestString)); // fire events to bring the count down to right at threshold for (long count = this.deviceconfig.InitialStockCount; count > this.deviceconfig.LowStockThreshold; count--) { MethodResponse myresult = fakeDeviceClient.directMethods[0](methodRequest, null).Result; TestContext.WriteLine(">> Current stock level: " + device.CurrentStockLevel); } // now that we're at the threshold, lets clear all previous events fakeDeviceClient.sendMessageLog.Clear(); // clear out all messages to this point TestContext.WriteLine(string.Empty); TestContext.WriteLine(">> Purchasing 1 more ticket. Should send low stock notification"); // purchase one more ticket. MethodResponse methodresult = fakeDeviceClient.directMethods[0](methodRequest, null).Result; // we expect 2 messages to have been sent Assert.AreEqual(fakeDeviceClient.sendMessageLog.Count, 2); // second message should make expected result LowStockRequest expectedRequest = new LowStockRequest() { DeviceId = deviceconfig.DeviceId, DeviceType = deviceconfig.DeviceType, StockLevel = (deviceconfig.LowStockThreshold - 1), }; // get actual message into an object so we can compare it LowStockRequest actualRequest = JsonConvert.DeserializeObject <LowStockRequest>(fakeDeviceClient.sendMessageLog[1]); TestContext.WriteLine(fakeDeviceClient.sendMessageLog[1]); // compare properties to make sure they're valid. Assert.AreEqual(actualRequest.DeviceId, expectedRequest.DeviceId); Assert.AreEqual(actualRequest.DeviceType, expectedRequest.DeviceType); Assert.AreEqual(actualRequest.MessageType, expectedRequest.MessageType); Assert.AreEqual(actualRequest.StockLevel, expectedRequest.StockLevel); TestContext.WriteLine(string.Empty); TestContext.WriteLine(">> Testing to make sure we don't have a second low stock warning .."); // make sure we don't throw the low stock warning again fakeDeviceClient.sendMessageLog.Clear(); // clear out all messages to this point // purchase one more ticket. methodresult = fakeDeviceClient.directMethods[0](methodRequest, null).Result; // we expect 2 messages to have been sent Assert.AreEqual(fakeDeviceClient.sendMessageLog.Count, 1); // reset the device to make sure ticket stock is reset using the desired property callback option device.SetDeviceStatusAsync(DeviceStatus.disabled).Wait(); // disable the device device.SetDeviceStatusAsync(DeviceStatus.enabled).Wait(); // enable the device // check the stock level Assert.AreEqual(deviceconfig.InitialStockCount, device.CurrentStockLevel, "Device stock levels were not reset back to initial after device rest"); }