diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/BC/TestBikeInfo.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/BC/TestBikeInfo.cs new file mode 100644 index 0000000..767faa7 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/BC/TestBikeInfo.cs @@ -0,0 +1,20 @@ +using NUnit.Framework; +using TINK.Model.Bike.BC; + +namespace TestTINKLib.Fixtures.ObjectTests.Bike.BC +{ + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestBikeInfo + { + [Test] + public void TestCtorCopyNull() + { + Assert.Throws( + () => new BikeInfo(null), + "Verify that no unspecific reference not set to... exception is thrown"); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/BC/TestBikeInfoMutable.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/BC/TestBikeInfoMutable.cs new file mode 100644 index 0000000..c163684 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/BC/TestBikeInfoMutable.cs @@ -0,0 +1,128 @@ +using NUnit.Framework; +using Rhino.Mocks; +using System; +using System.Collections.Generic; +using TINK.Model.Bike; +using TINK.Model.Bikes.Bike; +using TINK.Model.State; + +using IBikeInfo = TINK.Model.Bike.BC.IBikeInfo; + +namespace TestTINKLib.Fixtures.ObjectTests.Bike +{ + [TestFixture] + public class TestBikeInfoMutable + { + private class BikeInfoMutable : TINK.Model.Bike.BC.BikeInfoMutable + { + public BikeInfoMutable( + string id, + bool isDemo = false, + IEnumerable group = null, + WheelType? wheelType = null, + TypeOfBike? typeOfBike = null, + string description = null, + string currentStationId = null, + Uri operatorUri = null, + TariffDescription tariffDescription = null, + Func dateTimeProvider = null, + IStateInfo stateInfo = null) : base(id, isDemo, group, wheelType, typeOfBike, description, currentStationId, operatorUri, tariffDescription, dateTimeProvider, stateInfo) + { + } + } + + [Test] + public void TestConstruct() + { + var l_oBikeInfo = new BikeInfoMutable("17"); + Assert.AreEqual("17", l_oBikeInfo.Id); + Assert.IsNull(l_oBikeInfo.CurrentStation); + Assert.AreEqual(InUseStateEnum.Disposable, l_oBikeInfo.State.Value); + Assert.AreEqual(null, l_oBikeInfo.WheelType); + Assert.AreEqual(null, l_oBikeInfo.TypeOfBike); + + l_oBikeInfo = new BikeInfoMutable("22", false, new List { "TINK" }, WheelType.Trike, TypeOfBike.Cargo, "Test description", "23"); + Assert.AreEqual("22", l_oBikeInfo.Id); + Assert.IsFalse(l_oBikeInfo.IsDemo); + Assert.AreEqual("23", l_oBikeInfo.CurrentStation); + Assert.AreEqual(InUseStateEnum.Disposable, l_oBikeInfo.State.Value); + Assert.AreEqual(WheelType.Trike, l_oBikeInfo.WheelType); + Assert.AreEqual(TypeOfBike.Cargo, l_oBikeInfo.TypeOfBike); + } + + [Test] + public void TestConstructCopy() + { + // State Booked + var l_oBikeInfoMock = MockRepository.GenerateStub(); + var l_oStateInfoMock = MockRepository.GenerateStub(); + + l_oBikeInfoMock.Stub(x => x.Id).Return("22"); + l_oBikeInfoMock.Stub(x => x.TypeOfBike).Return(TypeOfBike.Cargo); + l_oBikeInfoMock.Stub(x => x.WheelType).Return(WheelType.Trike); + l_oBikeInfoMock.Stub(x => x.CurrentStation).Return("23"); + l_oBikeInfoMock.Stub(x => x.State).Return(l_oStateInfoMock); + l_oStateInfoMock.Stub(x => x.Value).Return(InUseStateEnum.Booked); + l_oStateInfoMock.Stub(x => x.From).Return(new System.DateTime(2018, 01, 03)); + l_oStateInfoMock.Stub(x => x.MailAddress).Return("a@b"); + l_oStateInfoMock.Stub(x => x.Code).Return("234"); + + var l_oBikeInfo = new TINK.Model.Bike.BC.BikeInfoMutable(l_oBikeInfoMock); + + Assert.AreEqual(InUseStateEnum.Booked, l_oBikeInfo.State.Value); + Assert.AreEqual("22", l_oBikeInfo.Id); + Assert.AreEqual("23", l_oBikeInfo.CurrentStation); + Assert.AreEqual(WheelType.Trike, l_oBikeInfo.WheelType); + Assert.AreEqual(TypeOfBike.Cargo, l_oBikeInfo.TypeOfBike); + Assert.AreEqual(InUseStateEnum.Booked, l_oBikeInfo.State.Value); + Assert.AreEqual(new System.DateTime(2018, 01, 03), l_oBikeInfo.State.From); + Assert.AreEqual("a@b", l_oBikeInfo.State.MailAddress); + + // State Reserved + l_oBikeInfoMock = MockRepository.GenerateStub(); + l_oStateInfoMock = MockRepository.GenerateStub(); + + l_oBikeInfoMock.Stub(x => x.Id).Return("22"); + l_oBikeInfoMock.Stub(x => x.TypeOfBike).Return(TypeOfBike.Cargo); + l_oBikeInfoMock.Stub(x => x.WheelType).Return(WheelType.Trike); + l_oBikeInfoMock.Stub(x => x.CurrentStation).Return("23"); + l_oBikeInfoMock.Stub(x => x.State).Return(l_oStateInfoMock); + l_oStateInfoMock.Stub(x => x.Value).Return(InUseStateEnum.Reserved); + l_oStateInfoMock.Stub(x => x.From).Return(new System.DateTime(2018, 01, 03)); + l_oStateInfoMock.Stub(x => x.MailAddress).Return("a@b"); + l_oStateInfoMock.Stub(x => x.Code).Return("234"); + + l_oBikeInfo = new TINK.Model.Bike.BC.BikeInfoMutable(l_oBikeInfoMock); + + Assert.AreEqual(InUseStateEnum.Reserved, l_oBikeInfo.State.Value); + Assert.AreEqual("22", l_oBikeInfo.Id); + Assert.AreEqual("23", l_oBikeInfo.CurrentStation); + Assert.AreEqual(WheelType.Trike, l_oBikeInfo.WheelType); + Assert.AreEqual(TypeOfBike.Cargo, l_oBikeInfo.TypeOfBike); + Assert.AreEqual(InUseStateEnum.Reserved, l_oBikeInfo.State.Value); + Assert.AreEqual(new System.DateTime(2018, 01, 03), l_oBikeInfo.State.From); + Assert.AreEqual("a@b", l_oBikeInfo.State.MailAddress); + + // State Disposable + l_oBikeInfoMock = MockRepository.GenerateStub(); + l_oStateInfoMock = MockRepository.GenerateStub(); + + l_oBikeInfoMock.Stub(x => x.Id).Return("22"); + l_oBikeInfoMock.Stub(x => x.TypeOfBike).Return(TypeOfBike.Cargo); + l_oBikeInfoMock.Stub(x => x.WheelType).Return(WheelType.Trike); + l_oBikeInfoMock.Stub(x => x.CurrentStation).Return("23"); + l_oBikeInfoMock.Stub(x => x.State).Return(l_oStateInfoMock); + l_oStateInfoMock.Stub(x => x.Value).Return(InUseStateEnum.Disposable); + + l_oBikeInfo = new TINK.Model.Bike.BC.BikeInfoMutable(l_oBikeInfoMock); + + Assert.AreEqual(InUseStateEnum.Disposable, l_oBikeInfo.State.Value); + Assert.AreEqual("22", l_oBikeInfo.Id); + Assert.AreEqual("23", l_oBikeInfo.CurrentStation); + Assert.AreEqual(WheelType.Trike, l_oBikeInfo.WheelType); + Assert.AreEqual(TypeOfBike.Cargo, l_oBikeInfo.TypeOfBike); + Assert.AreEqual(InUseStateEnum.Disposable, l_oBikeInfo.State.Value); + Assert.IsNull(l_oBikeInfo.State.From); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestBikeInfo.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestBikeInfo.cs new file mode 100644 index 0000000..4f40e11 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestBikeInfo.cs @@ -0,0 +1,50 @@ +using NUnit.Framework; +using System; +using TINK.Model.Bike.BluetoothLock; +using TINK.Model.State; + +namespace TestTINKLib.Fixtures.ObjectTests.Bike.BluetoothLock +{ + [TestFixture] + public class TestBikeInfo + { + /// + /// Moved to TestShareeLib (.Net Core) + /// + [Test] + public void TestCtorCopyNull() + { + Assert.Throws( + () => new BikeInfo(null, null), + "Verify that no unspecific reference not set to... exception is thrown"); + + Assert.Throws( + () => new BikeInfo(new TINK.Model.Bike.BC.BikeInfo(12,1), null), + "Verify that no unspecific reference not set to... exception is thrown"); + } + + [Test] + public void TestCtorAvailable() + { + Assert.AreEqual (12,new BikeInfo(12, 13).Id); + Assert.AreEqual(13, new BikeInfo(12, 13).CurrentStation); + Assert.AreEqual(InUseStateEnum.Disposable, new BikeInfo(12, 13).State.Value); + } + + [Test] + public void TestCtorRequested() + { + Assert.AreEqual(12, new BikeInfo(12, 112, new Guid(), null, null, null, new DateTime(2020, 1, 1), "a@b", 13, dateTimeProvider: () => new DateTime(2019, 1, 1)).Id); + Assert.AreEqual(112, new BikeInfo(12, 112, new Guid(), null, null, null, new DateTime(2020,1,1), "a@b", 13, dateTimeProvider: () => new DateTime(2019, 1, 1)).LockInfo.Id); + Assert.AreEqual(InUseStateEnum.Reserved, new BikeInfo(12, 112, new Guid(), null, null, null, new DateTime(2020, 1, 1), "a@b", 13, dateTimeProvider: () => new DateTime(2019, 1, 1)).State.Value); + } + + [Test] + public void TestCtorBooked() + { + Assert.AreEqual(12, new BikeInfo(12, 112, new Guid(), null, null, null, new DateTime(2020, 1, 1), "a@b", 13).Id); + Assert.AreEqual(112, new BikeInfo(12, 112, new Guid(), null, null, null, new DateTime(2020, 1, 1), "a@b", 13).LockInfo.Id); + Assert.AreEqual(InUseStateEnum.Booked, new BikeInfo(12, 112, new Guid(), null, null, null, new DateTime(2020, 1, 1), "a@b", 13).State.Value); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestLockInfo.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestLockInfo.cs new file mode 100644 index 0000000..5e83381 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestLockInfo.cs @@ -0,0 +1,155 @@ +using NUnit.Framework; +using System; +using TINK.Model.Bike.BluetoothLock; +namespace TestTINKLib.Fixtures.ObjectTests.Bike.BluetoothLock +{ + [TestFixture] + public class TestLockInfo + { + /// + /// Moved to TestShareeLib (.Net Core) + /// + [Test] + public void TestCtor() + { + Assert.AreEqual( + LockingState.Unknown, + new LockInfo.Builder { Id = 123 }.Build().State); + + Assert.AreEqual( + 123, + new LockInfo.Builder { Id = 123 }.Build().Id); + } + + [Test] + public void TestEquals() + { + Assert.IsTrue(new LockInfo.Builder { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed}.Build() == new LockInfo.Builder { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build()); + } + + [Test] + public void TestEqualsFalse() + { + Assert.IsFalse(new LockInfo.Builder + { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build() == new LockInfo.Builder + { + Id = 3, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build()); + + Assert.IsFalse(new LockInfo.Builder + { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build() == new LockInfo.Builder + { + Id = 2, + Guid = new Guid("1000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build()); + + Assert.IsFalse(new LockInfo.Builder + { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build() == new LockInfo.Builder + { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 5, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build()); + + Assert.IsFalse(new LockInfo.Builder + { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build() == new LockInfo.Builder + { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 9, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build()); + + Assert.IsFalse(new LockInfo.Builder + { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build() == new LockInfo.Builder + { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 11, 1 }, + State = LockingState.Closed + }.Build()); + + Assert.IsFalse(new LockInfo.Builder + { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Closed + }.Build() == new LockInfo.Builder + { + Id = 2, + Guid = new Guid("0000f00d-1212-efde-1523-785fef13d123"), + Seed = new byte[] { 1, 2 }, + UserKey = new byte[] { 7, 2 }, + AdminKey = new byte[] { 2, 1 }, + State = LockingState.Open + }.Build()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestLockInfoHelper.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestLockInfoHelper.cs new file mode 100644 index 0000000..8956633 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestLockInfoHelper.cs @@ -0,0 +1,56 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using TINK.Model.Bike.BluetoothLock; +using TINK.Services.BluetoothLock.Tdo; + +namespace TestTINKLib.Fixtures.ObjectTests.Bike.BluetoothLock +{ + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestLockInfoHelper + { + [Test] + public void TestUpdateById_State() + { + var locksInfo = new List { + new LockInfo.Builder { Id = 12, Seed = new byte[] { 3, 5 }, UserKey = new byte[] {2, 1 }, State = LockingState.Unknown }.Build(), + new LockInfo.Builder { Id = 14, Seed = new byte[] { 3, 1 }, UserKey = new byte[] {2, 7 }, State = LockingState.Open }.Build(), + new LockInfo.Builder { Id = 3, Seed = new byte[] { 1, 5 }, UserKey = new byte[] {2, 9 }, State = LockingState.Closed }.Build(), + }; + + var locksInfoTdo = new List { + new LockInfoTdo.Builder { Id =14, State = LockitLockingState.Closed}.Build() + }; + + var resultList = locksInfo.UpdateById(locksInfoTdo); + + var result = resultList.FirstOrDefault(x => x.Id == 14); + Assert.NotNull(result, "Target element was removed."); + Assert.AreEqual(LockingState.Closed, result.State); + } + + [Test] + public void TestUpdateById_Guid() + { + var locksInfo = new List { + new LockInfo.Builder { Id = 12, Seed = new byte[] { 3, 5 }, UserKey = new byte[] {2, 1 }, State = LockingState.Unknown }.Build(), + new LockInfo.Builder { Id = 14, Seed = new byte[] { 3, 1 }, UserKey = new byte[] {2, 7 }, State = LockingState.Open }.Build(), + new LockInfo.Builder { Id = 3, Seed = new byte[] { 1, 5 }, UserKey = new byte[] {2, 9 }, State = LockingState.Closed }.Build(), + }; + + var locksInfoTdo = new List { + new LockInfoTdo.Builder { Id =14, Guid = new System.Guid("00000000-0000-0000-0000-e57e6b9aee16"), State = LockitLockingState.Open}.Build() + }; + + var resultList = locksInfo.UpdateById(locksInfoTdo); + + var result = resultList.FirstOrDefault(x => x.Id == 14); + Assert.NotNull(result, "Target element was removed."); + Assert.AreEqual(new Guid("00000000-0000-0000-0000-e57e6b9aee16"), result.Guid); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestLockInfoMutable.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestLockInfoMutable.cs new file mode 100644 index 0000000..2b15d30 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/BluetoothLock/TestLockInfoMutable.cs @@ -0,0 +1,39 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TINK.Model.Bike.BluetoothLock; + +namespace TestTINKLib.Fixtures.ObjectTests.Bike.BluetoothLock +{ + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestLockInfoMutable + { + [Test] + public void TestCtor() + { + var lockInfo = new LockInfoMutable(1, new Guid(), new byte[] { 1, 2, 3 }, new byte[] { 1, 23 }, new byte[] { 1, 12 }, LockingState.Closed); + + Assert.AreEqual(1, lockInfo.Id); + Assert.AreEqual(new Guid(), lockInfo.Guid); + Assert.IsTrue((new byte[] { 1, 2, 3 }).SequenceEqual(lockInfo.UserKey)); + Assert.IsTrue((new byte[] { 1, 23 }).SequenceEqual(lockInfo.AdminKey)); + Assert.IsTrue((new byte[] { 1, 12 }).SequenceEqual(lockInfo.Seed)); + Assert.AreEqual(LockingState.Closed, lockInfo.State); + } + + [Test] + public void TestSetGuid() + { + var lockInfo = new LockInfoMutable(1, new Guid(), new byte[] { 1, 2, 3 }, new byte[] { 1, 23 }, new byte[] { 1, 12 }, LockingState.Closed); + + lockInfo.Guid = new Guid("00000000-0000-0000-0000-e57e6b9aee16"); + Assert.AreEqual(new Guid("00000000-0000-0000-0000-e57e6b9aee16"), lockInfo.Guid); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/TestBike.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBike.cs new file mode 100644 index 0000000..715e962 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBike.cs @@ -0,0 +1,49 @@ +using NUnit.Framework; + +namespace TestTINKLib.Fixtures.ObjectTests.Bike +{ + using TINK.Model.Bike; + + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestBike + { + [Test] + public void TestConstruct() + { + var l_oBike = new Bike(43); + Assert.AreEqual(43, l_oBike.Id); + Assert.AreEqual(null, l_oBike.TypeOfBike); + Assert.AreEqual(null, l_oBike.WheelType); + + l_oBike = new Bike(43, WheelType.Mono, TypeOfBike.Cargo); + + Assert.AreEqual(43, l_oBike.Id); + Assert.AreEqual(TypeOfBike.Cargo, l_oBike.TypeOfBike); + Assert.AreEqual(WheelType.Mono, l_oBike.WheelType); + } + + [Test] + public void TestCompare() + { + var l_oBike1 = new Bike(43); + Assert.AreEqual(43, l_oBike1.Id); + Assert.AreEqual(null, l_oBike1.TypeOfBike); + Assert.AreEqual(null, l_oBike1.WheelType); + + var l_oBike2 = new Bike(42, WheelType.Two, TypeOfBike.Allround); + Assert.IsFalse(l_oBike1 == l_oBike2); + + l_oBike2 = new Bike(43, WheelType.Mono, TypeOfBike.Allround); + Assert.IsFalse(l_oBike1 == l_oBike2); + + l_oBike2 = new Bike(43, WheelType.Two, TypeOfBike.Cargo); + Assert.IsFalse(l_oBike1 == l_oBike2); + + l_oBike2 = new Bike(43, null, null); + Assert.IsTrue(l_oBike1 == l_oBike2); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollection.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollection.cs new file mode 100644 index 0000000..95a8f78 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollection.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using System; +using TINK.Model.Bike; + + +namespace TestTINKLib +{ + + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestBikeCollection + { + /// Tests the member. + [Test] + public void TestConstruct() + { + var l_oColl = new BikeCollection(); + + Assert.AreEqual(0, l_oColl.Count); + Assert.IsNull(l_oColl.GetById(1)); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollectionFilter.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollectionFilter.cs new file mode 100644 index 0000000..f8d90f7 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollectionFilter.cs @@ -0,0 +1,78 @@ +using NUnit.Framework; +using System.Collections.Generic; +using TINK.Model; +using TINK.Model.Bike; + +namespace TestTINKLib.Fixtures.ObjectTests.Bike +{ + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestBikeCollectionFilter + { + [Test] + public void TestGetAtStation() + { + var coll = new BikeCollection( + new Dictionary + { + {3, new TINK.Model.Bike.BC.BikeInfo(7, 3 /* Stadion id */) }, + {7, new TINK.Model.Bike.BC.BikeInfo(8, 12 /* Stadion id */) }, + {12, new TINK.Model.Bike.BC.BikeInfo(33, 12 /* Stadion id */) } + }); + + Assert.AreEqual( + 0, + BikeCollectionFilter.GetAtStation(null, 12).Count); + + Assert.AreEqual( + 0, + coll.GetAtStation(null).Count); + + Assert.AreEqual( + 0, + coll.GetAtStation(22).Count); + + Assert.AreEqual( + 1, + coll.GetAtStation(3).Count); + + Assert.AreEqual( + 2, + coll.GetAtStation(12).Count); + } + + [Test] + public void TestGetLockIt() + { + var coll = new BikeCollection( + new Dictionary + { + {7, new TINK.Model.Bike.BC.BikeInfo(8, 12 /* Stadion id */) }, + {12, new TINK.Model.Bike.BC.BikeInfo(33, 12 /* Stadion id */) } + }); + + + Assert.AreEqual( + 0, + coll.GetLockIt().Count); + + coll = new BikeCollection( + new Dictionary + { + {7, new TINK.Model.Bike.BC.BikeInfo(8, 12 /* Stadion id */) }, + {11, new TINK.Model.Bike.BluetoothLock.BikeInfo(33, 12 /* Stadion id */) }, + {12, new TINK.Model.Bike.BC.BikeInfo(33, 12 /* Stadion id */) } + }); + + Assert.AreEqual( + 0, + BikeCollectionFilter.GetLockIt(null).Count); + + Assert.AreEqual( + 1, + coll.GetLockIt().Count); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollectionMutable.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollectionMutable.cs new file mode 100644 index 0000000..a67b6ea --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollectionMutable.cs @@ -0,0 +1,93 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using TINK.Model.Bike; +using TINK.Model.State; + +using BikeInfo = TINK.Model.Bike.BC.BikeInfo; +using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable; + +namespace TestTINKLib +{ + + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestBikeCollectionMutable + { + /// Tests the member. + [Test] + public void TestAdd() + { + var l_oColl = new BikeCollectionMutable(); + + l_oColl.Add(new BikeInfoMutable(57, false, new List { "TINK" }, WheelType.Two, TypeOfBike.Allround)); + + Assert.Throws(() => l_oColl.Add(new BikeInfoMutable(57, false, new List { "TINK" }, WheelType.Trike, TypeOfBike.Cargo))); + } + + [Test] + public void TestUpdate_Null() + { + var l_oBikeRequested = new BikeInfoMutable(20, false, new List { "TINK" }, WheelType.Trike, TypeOfBike.Allround); + l_oBikeRequested.State.Load(new StateInfo(() => DateTime.Now, DateTime.Now, "john@long", "1234")); + + var l_oBikeColl = new BikeCollectionMutable + { + new BikeInfoMutable(63, false, new List { "TINK" }, WheelType.Two, TypeOfBike.Allround), + new BikeInfoMutable(57, false, new List { "TINK" }, WheelType.Trike, TypeOfBike.Cargo), + l_oBikeRequested, + }; + + // Verify initial state + Assert.NotNull(l_oBikeColl.GetById("63")); + Assert.AreEqual(InUseStateEnum.Disposable, l_oBikeColl.GetById("57").State.Value); + Assert.AreEqual(InUseStateEnum.Reserved, l_oBikeColl.GetById("20").State.Value); + Assert.Null(l_oBikeColl.GetById("33")); + + l_oBikeColl.Update(null); + + // Verify modified state + Assert.Null(l_oBikeColl.GetById("63")); + Assert.Null(l_oBikeColl.GetById("57")); + Assert.Null(l_oBikeColl.GetById("20")); + Assert.Null(l_oBikeColl.GetById("33")); + } + + [Test] + public void TestUpdate() + { + var l_oBikeRequested = new BikeInfoMutable(20, false, new List { "TINK" }, WheelType.Trike, TypeOfBike.Allround); + l_oBikeRequested.State.Load(new StateInfo(() => DateTime.Now, DateTime.Now, "john@long", "1234")); + + var l_oBikeColl = new BikeCollectionMutable + { + new BikeInfoMutable(63, false, new List { "TINK" }, WheelType.Two, TypeOfBike.Allround), + new BikeInfoMutable(57, false, new List { "TINK" }, WheelType.Trike, TypeOfBike.Cargo), + l_oBikeRequested, + }; + + // Verify initial state + Assert.NotNull(l_oBikeColl.GetById("63")); // Will be removed + Assert.AreEqual(InUseStateEnum.Disposable, l_oBikeColl.GetById("57").State.Value); // Will be requested + Assert.AreEqual(InUseStateEnum.Reserved, l_oBikeColl.GetById("20").State.Value); // Will be booked + Assert.Null(l_oBikeColl.GetById("33")); // + + var l_oBikeResponse = new List + { + new BikeInfo("57", false, new [] { "TINK" }, WheelType.Trike, TypeOfBike.Allround, "Test description", 7, DateTime.Now, "john@long,", "1234"), + new BikeInfo("20", false, new [] {"TINK" }, WheelType.Trike, TypeOfBike.Allround, "Test description", 7, DateTime.Now, "john@long,", "1234"), + new BikeInfo("33", 7, false, new [] {"TINK" }, WheelType.Trike, TypeOfBike.Allround), + }; + + l_oBikeColl.Update(l_oBikeResponse); + + // Verify modified state + Assert.Null(l_oBikeColl.GetById("63")); + Assert.AreEqual(InUseStateEnum.Booked, l_oBikeColl.GetById("57").State.Value); + Assert.AreEqual(InUseStateEnum.Booked, l_oBikeColl.GetById("20").State.Value); + Assert.NotNull(l_oBikeColl.GetById("33")); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollectionSerializeJSON.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollectionSerializeJSON.cs new file mode 100644 index 0000000..b264970 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeCollectionSerializeJSON.cs @@ -0,0 +1,68 @@ +using NUnit.Framework; +using TINK.Model.Bike; + +using Newtonsoft.Json; +using TINK.Model.State; +using System.Collections.Generic; + +using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable; + +namespace TestTINKLib +{ + + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestBikeCollectionSerializeJSON + { + [Test, Ignore("Disabled because serialization does no more work due to inheritance since commit ec14b93b.")] + public void Test_SerializeJSON() + { + var l_oCollSource = new BikeCollectionMutable + { + new BikeInfoMutable(57, false, new List { "TINK" }, WheelType.Two, TypeOfBike.Allround) + }; + + // Verify prerequisites. + Assert.AreEqual(1, l_oCollSource.Count); + Assert.AreEqual(57, l_oCollSource[0].Id); + Assert.AreEqual(InUseStateEnum.Disposable, l_oCollSource[0].State.Value); + Assert.IsNull(l_oCollSource[0].State.MailAddress); + Assert.IsNull(l_oCollSource[0].State.Code); + Assert.IsNull(l_oCollSource[0].State.From); + + // Serialize object and verify json + var l_oDetected = JsonConvert.SerializeObject(l_oCollSource, Formatting.Indented, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); + const string EXPECTED = @" + [ + { + ""Id"": 57, + ""CurrentStation"": null, + ""WheelType"": 1, + ""TypeOfBike"": 0, + ""State"": { + ""StateInfoObject"": { + ""$type"": ""TINK.Model.State.StateAvailableInfo, TINKLib"" + } + } + } + ]"; + + Assert.AreEqual( + TestHelper.PrepareXmlForStringCompare(EXPECTED), + TestHelper.PrepareXmlForStringCompare(l_oDetected)); + + // Deserialize object. + var l_oBikeCollectionTarget = JsonConvert.DeserializeObject(l_oDetected, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); + + // Verify state. + Assert.AreEqual(1, l_oBikeCollectionTarget.Count); + Assert.AreEqual(57, l_oBikeCollectionTarget[0].Id); + Assert.AreEqual(InUseStateEnum.Disposable, l_oBikeCollectionTarget[0].State.Value); + Assert.IsNull(l_oBikeCollectionTarget[0].State.MailAddress); + Assert.IsNull(l_oBikeCollectionTarget[0].State.Code); + Assert.IsNull(l_oBikeCollectionTarget[0].State.From); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeMutable.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeMutable.cs new file mode 100644 index 0000000..2337035 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeMutable.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using TINK.Model.Bike; +using TINK.Model.State; + +using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable; + +namespace TestTINKLib +{ + + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + class TestBikeMutable + { + [Test] + public void TestConstruct() + { + var l_oBike = new BikeInfoMutable( + 42, + false, + new List { "TINK" }, + WheelType.Two, + TypeOfBike.Cargo); + + Assert.AreEqual(42, l_oBike.Id); + Assert.IsFalse(l_oBike.IsDemo); + Assert.AreEqual(WheelType.Two, l_oBike.WheelType); + Assert.AreEqual(TypeOfBike.Cargo, l_oBike.TypeOfBike); + Assert.AreEqual(InUseStateEnum.Disposable, l_oBike.State.Value); + Assert.IsNull(l_oBike.CurrentStation); + + l_oBike = new BikeInfoMutable( + 17, + true, + new List { "TINK" }, + WheelType.Mono, + TypeOfBike.Allround, + "Test description", + 1); + + Assert.AreEqual(17, l_oBike.Id); + Assert.IsTrue(l_oBike.IsDemo); + Assert.AreEqual(WheelType.Mono, l_oBike.WheelType); + Assert.AreEqual(TypeOfBike.Allround, l_oBike.TypeOfBike); + Assert.AreEqual(InUseStateEnum.Disposable, l_oBike.State.Value); + Assert.AreEqual(1, l_oBike.CurrentStation); + } + + [Test] + public void TestToString() + { + var l_oBike = new BikeInfoMutable(3, false, new List { "TINK" }, WheelType.Two, TypeOfBike.Cargo, p_oDateTimeProvider: () => new DateTime(1970, 1, 1)); + + Assert.AreEqual( + "Id=3, wheel(s)=Two, type=Cargo, demo=False, state=Disposable, location=On the road.", + l_oBike.ToString()); + + l_oBike = new BikeInfoMutable(3, true, new List { "TINK" }, WheelType.Trike, TypeOfBike.Allround, "Test description", 5, p_oDateTimeProvider: () => new DateTime(1970, 1, 1)); + Assert.AreEqual( + "Id=3, wheel(s)=Trike, type=Allround, demo=True, state=Disposable, location=Station 5.", + l_oBike.ToString()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeSerializeJSON.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeSerializeJSON.cs new file mode 100644 index 0000000..bb28029 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikeSerializeJSON.cs @@ -0,0 +1,59 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using TINK.Model.Bike; +using TINK.Model.State; + +using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable; + +namespace TestTINKLib +{ + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestBikeSerializeJSON + { + [Test, Ignore("Disabled because serialization does no more work due to inheritance since commit ec14b93b.")] + public void TestConstruct_SerializeJSON_Disposable() + { + // Create object to test. + var l_oBikeSource = new BikeInfoMutable(3, false, new List { "TINK" }, WheelType.Two, TypeOfBike.Cargo, p_oDateTimeProvider: () => new DateTime(1970, 1, 1)); + + // Verify prerequisites + Assert.AreEqual(InUseStateEnum.Disposable, l_oBikeSource.State.Value); + Assert.IsNull(l_oBikeSource.State.MailAddress); + Assert.IsNull(l_oBikeSource.State.Code); + Assert.IsNull(l_oBikeSource.State.From); + + // Serialize object and verify. + var l_oDetected = JsonConvert.SerializeObject(l_oBikeSource, Formatting.Indented, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); + const string EXPECTED = @" + { + ""Id"": 3, + ""CurrentStation"": null, + ""WheelType"": 1, + ""TypeOfBike"": 1, + ""State"": { + ""StateInfoObject"": { + ""$type"": ""TINK.Model.State.StateAvailableInfo, TINKLib"" + } + } + }"; + + Assert.AreEqual( + TestHelper.PrepareXmlForStringCompare(EXPECTED), + TestHelper.PrepareXmlForStringCompare(l_oDetected)); + + // Deserialize object. + var l_oBikeTarget = JsonConvert.DeserializeObject(l_oDetected, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); + + // Verify state. + Assert.AreEqual(InUseStateEnum.Disposable, l_oBikeTarget.State.Value); + Assert.IsNull(l_oBikeTarget.State.MailAddress); + Assert.IsNull(l_oBikeTarget.State.Code); + Assert.IsNull(l_oBikeTarget.State.From); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikesAvailableResponse.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikesAvailableResponse.cs new file mode 100644 index 0000000..1f52744 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/TestBikesAvailableResponse.cs @@ -0,0 +1,69 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using TINK.Repository.Response; + +namespace TestTINKLib.Fixtures.ObjectTests.Bike +{ + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestBikesAvailableResponse + { + [Test] + public void TestDeserializeStationEmpty() + { + // Response for bike 231 is a real world answer. + var l_oBikes = JsonConvert.DeserializeObject(@" + { + ""apiserver"" : ""https://app.tink-konstanz.de"", + ""response"" : ""bikes_available"", + ""bikes"" : { + ""231"" : { + ""bike"" : ""231"", + ""description"" : ""Stadtrad"", + ""system"" : ""BC"", + ""bike_group"" : [ ""Konrad"" ], + ""station"" : """", + ""state"" : ""available"", + ""gps"" : { ""latitude"": ""9.1594501"", ""longitude"": ""47.6749928"" } + } + }, + ""copri_version"" : ""4.1.0.0"", + ""authcookie"" : """", + ""response_state"" : ""OK"" + } + "); + + Assert.IsNull(l_oBikes.bikes[231].station); + } + + [Test] + public void TestDeserializeStationInfoMissing() + { + // Response for bike 231 might not be real world answer. + var l_oBikes = JsonConvert.DeserializeObject(@" + { + ""apiserver"" : ""https://app.tink-konstanz.de"", + ""response"" : ""bikes_available"", + ""bikes"" : { + ""231"" : { + ""bike"" : ""231"", + ""description"" : ""Stadtrad"", + ""system"" : ""BC"", + ""bike_group"" : [ ""Konrad"" ], + ""state"" : ""available"", + ""gps"" : { ""latitude"": ""9.1594501"", ""longitude"": ""47.6749928"" } + } + }, + ""copri_version"" : ""4.1.0.0"", + ""authcookie"" : """", + ""response_state"" : ""OK"" + } + "); + + Assert.IsNull(l_oBikes.bikes[231].station); + + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Bike/TestStateBookedInfoSerializeJSON.cs b/TestTINKLib/Fixtures/ObjectTests/Bike/TestStateBookedInfoSerializeJSON.cs new file mode 100644 index 0000000..65a8bbd --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Bike/TestStateBookedInfoSerializeJSON.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using TINK.Model.State; + + +namespace TestTINKLib.Fixtures.Bike +{ + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestStateBookedInfoSerializeJSON + { + [Test] + public void TestSerializeJSON() + { + // Create object to test. + var l_oInfoSource = new StateOccupiedInfo( + new DateTime(2017, 09, 20, 23, 05, 0), + "Heinz@mueller", + "17 xxb"); + + // Serialize object and verify. + var l_oDetected = JsonConvert.SerializeObject(l_oInfoSource); + const string EXPECTED = @" + { + ""From"":""2017 - 09 - 20T23: 05:00"", + ""MailAddress"":""Heinz@mueller"", + ""Code"":""17 xxb"" + }"; + Assert.AreEqual( + TestHelper.PrepareXmlForStringCompare(EXPECTED.Replace("\n", string.Empty).Replace("\r", string.Empty)), + TestHelper.PrepareXmlForStringCompare(l_oDetected.Replace("\n", string.Empty).Replace("\r", string.Empty))); + + // Deserialize object and verify. + var l_oInfoTarget = JsonConvert.DeserializeObject(l_oDetected); + Assert.AreEqual(InUseStateEnum.Booked, l_oInfoTarget.Value); + Assert.AreEqual("Heinz@mueller", l_oInfoTarget.MailAddress); + Assert.AreEqual("17 xxb", l_oInfoTarget.Code); + Assert.AreEqual(new DateTime(2017, 9, 20, 23, 5, 0), l_oInfoTarget.From); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/CopriCallsHttpReference.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/CopriCallsHttpReference.cs new file mode 100644 index 0000000..f9e67a9 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/CopriCallsHttpReference.cs @@ -0,0 +1,290 @@ + +using Newtonsoft.Json; +using System; +using System.IO; +using System.Net; +using System.Text; +using TINK.Repository; +using TINK.Repository.Exception; +using TINK.Repository.Response; + +namespace UITest.Fixtures.Connector +{ + /// + /// Object which manages calls to copri. + /// + public class CopriCallsHttpsReference + { + /// Logs user in. + /// Host to connect to. + /// Id of the merchant. + /// Mailaddress of user to log in. + /// Password to log in. + /// Id specifying user and hardware. + /// Response which holds auth cookie + public static AuthorizationResponse DoAuthorizeCall( + string copriHost, + string merchantId, + string mailAddress, + string password, + string deviceId) + { +#if !WINDOWS_UWP + + var l_oCommand = string.Format( + "request=authorization&merchant_id={0}&user_id={1}&user_pw={2}&hw_id={3}", + merchantId, + mailAddress, + password, + deviceId); + + /// Extract session cookie from response. + + string response = string.Empty; + + response = Post(l_oCommand, copriHost); + + return JsonConvert.DeserializeObject>(response)?.shareejson; +#else + return null; +#endif + } + + /// Logs user out. + /// Host to connect to. + /// Id of the merchant. + /// Cookie which identifies user. + public static AuthorizationoutResponse DoAuthoutCall( + string copriHost, + string merchantId, + string sessionCookie) + { +#if !WINDOWS_UWP + + var l_oCommand = string.Format( + "request=authout&authcookie={0}{1}", + sessionCookie, + merchantId); + + string l_oLogoutResponse; + + l_oLogoutResponse = Post(l_oCommand, copriHost); + + /// Extract session cookie from response. + return JsonConvert.DeserializeObject>(l_oLogoutResponse)?.shareejson; +#else + return null; +#endif + } + + /// + /// Get list of stations from file. + /// + /// URL of the copri host to connect to. + /// Id of the merchant. + /// Auto cookie of user if user is logged in. + /// List of files. + public static StationsAllResponse GetStationsAllCall( + string p_strCopriHost, + string p_strMerchantId, + string p_strCookie = null) + { + var l_oCommand = string.Format( + "request=stations_all&authcookie={0}{1}", + p_strCookie, + p_strMerchantId); + +#if !WINDOWS_UWP + string l_oStationsAllResponse; + + l_oStationsAllResponse = Post(l_oCommand, p_strCopriHost); + + // Extract bikes from response. + return JsonConvert.DeserializeObject>(l_oStationsAllResponse)?.shareejson; +#else + return null; +#endif + } + + /// + /// Gets a list of bikes from Copri. + /// + /// URL of the copri host to connect to. + /// Id of the merchant. + /// Cookie to authenticate user. + /// Response holding list of bikes. + public static BikesAvailableResponse GetBikesAvailableCall( + string copriHost, + string merchantId, + string sessionCookie = null) + { +#if !WINDOWS_UWP + string l_oCommand = + $"request=bikes_available&system=all&authcookie={sessionCookie ?? string.Empty}{merchantId}"; + + string response; + + response = Post(l_oCommand, copriHost); + + // Extract bikes from response. + return CopriCallsStatic.DeserializeResponse(response); +#else + return null; +#endif + } + + /// + /// Gets a list of bikes reserved/ booked by acctive user from Copri. + /// + /// Id of the merchant. + /// Cookie to authenticate user. + /// Response holding list of bikes. + public static BikesReservedOccupiedResponse GetBikesOccupiedCall( + string copriHost, + string merchantId, + string sessionCookie) + { +#if !WINDOWS_UWP + string l_oCommand = !string.IsNullOrEmpty(sessionCookie) + ? $"request=user_bikes_occupied&system=all&authcookie={sessionCookie}{merchantId}" + : "request=bikes_available"; + + string l_oBikesOccupiedResponse; + + l_oBikesOccupiedResponse = Post(l_oCommand, copriHost); + + // Extract bikes from response. + return CopriCallsStatic.DeserializeResponse(l_oBikesOccupiedResponse); +#else + return null; +#endif + } + + /// + /// Gets booking request response. + /// + /// Id of the merchant. + /// Id of the bike to book. + /// Cookie identifying the user. + /// Response on booking request. + public static ReservationBookingResponse DoReserveCall( + string copriHost, + string p_strMerchantId, + string p_iBikeId, + string p_strSessionCookie) + { +#if !WINDOWS_UWP + string l_oCommand = string.Format( + "request=booking_request&bike={0}&authcookie={1}{2}", + p_iBikeId, + p_strSessionCookie, + p_strMerchantId); + + string l_oBikesAvaialbeResponse = Post(l_oCommand, copriHost); + + // Extract bikes from response. + return JsonConvert.DeserializeObject>(l_oBikesAvaialbeResponse)?.shareejson; +#else + return null; +#endif + } + + /// + /// Gets canel booking request response. + /// + /// Id of the merchant. + /// Id of the bike to book. + /// Cookie of the logged in user. + /// Response on cancel booking request. + public static ReservationCancelReturnResponse DoCancelReservationCall( + string copriHost, + string p_strMerchantId, + string p_iBikeId, + string p_strSessionCookie) + { +#if !WINDOWS_UWP + string l_oCommand = string.Format( + "request=booking_cancel&bike={0}&authcookie={1}{2}", + p_iBikeId, + p_strSessionCookie, + p_strMerchantId); + string l_oBikesAvaialbeResponse; + + l_oBikesAvaialbeResponse = Post(l_oCommand, copriHost); + + // Extract bikes from response. + return JsonConvert.DeserializeObject>(l_oBikesAvaialbeResponse)?.shareejson; +#else + return null; +#endif + } + + /// + /// Gets a list of bikes from Copri. + /// + /// Cookie to authenticate user. + /// + private static string Post( + string p_strCommand, + string p_strURL) + { +#if !WINDOWS_UWP + var l_strHost = p_strURL; + + // Returns a http request. + var l_oRequest = WebRequest.Create(l_strHost); + + l_oRequest.Method = "POST"; + l_oRequest.ContentType = "application/x-www-form-urlencoded"; + + byte[] l_oPostData = Encoding.UTF8.GetBytes(p_strCommand); + + l_oRequest.ContentLength = l_oPostData.Length; + + // Get the request stream. + using (Stream l_oDataStream = l_oRequest.GetRequestStream()) + { + // Write the data to the request stream. + l_oDataStream.Write(l_oPostData, 0, l_oPostData.Length); + } + + // Get the response. + var l_oResponse = l_oRequest.GetResponse() as HttpWebResponse; + + if (l_oResponse == null) + { + throw new Exception(string.Format("Reserve request failed. Response form from server was not of expected type.")); + } + + if (l_oResponse.StatusCode != HttpStatusCode.OK) + { + throw new CommunicationException(string.Format( + "Posting request {0} failed. Expected status code is {1} but was {2}.", + p_strCommand, + HttpStatusCode.OK, + l_oResponse.StatusCode)); + } + + string responseFromServer = string.Empty; + + // Get the request stream. + using (Stream l_oDataStream = l_oResponse.GetResponseStream()) + using (StreamReader l_oReader = new StreamReader(l_oDataStream)) + { + // Read the content. + responseFromServer = l_oReader.ReadToEnd(); + + // Display the content. + Console.WriteLine(responseFromServer); + + // Clean up the streams. + l_oResponse.Close(); + } + + return responseFromServer; +#else + return null; +#endif + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Exception/TestAuthcookieNotDefinedException.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Exception/TestAuthcookieNotDefinedException.cs new file mode 100644 index 0000000..2df5c89 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Exception/TestAuthcookieNotDefinedException.cs @@ -0,0 +1,49 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using TINK.Repository.Exception; +using TINK.Repository.Response; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector.Exception +{ + [TestFixture] + public class TestAuthcookieNotDefinedException + { + [Test] + public void TestConstruct() + { + Assert.AreEqual( + "Can not test.\r\nDie Sitzung ist abgelaufen. Bitte neu anmelden.", + (new AuthcookieNotDefinedException( + "Can not test.", + JsonConvert.DeserializeObject(@"{ ""response_state"" : ""Some inner error description""}"))).Message); + } + + [Test] + public void TestTestIsAuthcookieNotDefined_False() + { + var response = JsonConvert.DeserializeObject(@"{ ""response_state"" : ""OK"" }"); + Assert.That(AuthcookieNotDefinedException.IsAuthcookieNotDefined(response, "Test context", out AuthcookieNotDefinedException exception), + Is.EqualTo(false)); + } + + [Test] + public void TestIsAuthcookieNotDefined_TrueLegacy() + { + var response = JsonConvert.DeserializeObject($"{{ \"response_state\" : \"Failure 1003: authcookie not defined\" }}"); + Assert.That(AuthcookieNotDefinedException.IsAuthcookieNotDefined(response, "Test context", out AuthcookieNotDefinedException exception), + Is.EqualTo(true)); + + Assert.That(exception, !Is.Null); + } + + [Test] + public void TestIsAuthcookieNotDefined_False() + { + var response = JsonConvert.DeserializeObject($"{{ \"response_state\" : \"Failure 1001: authcookie not defined\" }}"); + Assert.That(AuthcookieNotDefinedException.IsAuthcookieNotDefined(response, "Test context", out AuthcookieNotDefinedException exception), + Is.EqualTo(true)); + + Assert.That(exception, !Is.Null); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Filter/TestIntersectFilter.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Filter/TestIntersectFilter.cs new file mode 100644 index 0000000..c0caba3 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Filter/TestIntersectFilter.cs @@ -0,0 +1,40 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using TINK.Model.Connector.Filter; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector.Filter +{ + [TestFixture] + public class TestIntersectFilter + { + [Test] + public void TestDoFilter_Null() + { + var filter = new IntersectGroupFilter(new List { "Tonk" }); + + Assert.AreEqual(1, filter.DoFilter(null).Count()); + Assert.AreEqual( + "Tonk", + filter.DoFilter(null).ToArray()[0], + "Do not apply filtering when null is passed as argement (null-filter)."); + } + + [Test] + public void TestDoFilter_Empty() + { + var filter = new IntersectGroupFilter(new List { "Tonk" }); + + Assert.AreEqual(0, filter.DoFilter(new List()).Count()); + } + + [Test] + public void TestDoFilter() + { + var filter = new IntersectGroupFilter(new List { "Tonk", "Honk" }); + + Assert.AreEqual(1, filter.DoFilter(new List { "Tonk", "Klonk" }).Count()); + Assert.AreEqual("Tonk", filter.DoFilter(new List { "Tonk", "Klonk" }).ToArray()[0]); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Filter/TestNullFilter.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Filter/TestNullFilter.cs new file mode 100644 index 0000000..ed11091 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Filter/TestNullFilter.cs @@ -0,0 +1,23 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TINK.Model.Connector.Filter; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector.Filter +{ + [TestFixture] + public class TestNullFilter + { + [Test] + public void TestDoFilter() + { + var filter = new NullGroupFilter(); + Assert.IsNull(filter.DoFilter(null)); + Assert.AreEqual(0, filter.DoFilter(new List()).Count()); + Assert.AreEqual(1, filter.DoFilter(new List { "Hello" }).Count()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestCachedQuery.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestCachedQuery.cs new file mode 100644 index 0000000..e655b14 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestCachedQuery.cs @@ -0,0 +1,189 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using Rhino.Mocks; +using System.Threading.Tasks; +using TINK.Model.Connector; +using TINK.Model.Services.CopriApi; +using TINK.Repository; +using TINK.Repository.Response; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector.Query +{ + [TestFixture] + public class TestCachedQuery + { + private const string BIKESAVAILABLE = @"{ + ""copri_version"" : ""4.1.0.0"", + ""bikes"" : {}, + ""response_state"" : ""OK"", + ""apiserver"" : ""https://app.tink-konstanz.de"", + ""authcookie"" : """", + ""response"" : ""bikes_available"", + ""bikes"" : { + ""2352"" : { + ""description"" : ""Cargo Long"", + ""state"" : ""available"", + ""bike"" : ""1"", + ""gps"" : { ""latitude"": ""47.669888"", ""longitude"": ""9.167749"" }, + ""station"" : ""9"" + } + } + }"; + + private const string STATIONSALL = @"{ + ""copri_version"" : ""4.1.0.0"", + ""stations"" : { + ""5"" : { + ""station"" : ""5"", + ""bike_soll"" : ""0"", + ""bike_ist"" : ""7"", + ""station_group"" : [ ""TINK"" ], + ""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" }, + ""state"" : ""available"", + ""description"" : """" + }, + ""13"" : { + ""station"" : ""13"", + ""bike_soll"" : ""4"", + ""bike_ist"" : ""1"", + ""station_group"" : [ ""TINK"" ], + ""gps"" : { ""latitude"": ""47.657756"", ""longitude"": ""9.176084"" }, + ""state"" : ""available"", + ""description"" : """" + }, + ""30"" : { + ""station"" : ""30"", + ""bike_soll"" : ""5"", + ""bike_ist"" : ""0"", + ""station_group"" : [ ""TINK"", ""Konrad"" ], + ""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" }, + ""state"" : ""available"", + ""description"" : ""Test für Stadtradstation"" + } + }, + ""user_group"" : [ ""Konrad"", ""TINK"" ], + ""response_state"" : ""OK"", + ""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"", + ""debuglevel"" : ""2"", + ""response"" : ""stations_all"", + ""user_id"" : ""javaminister@gmail.com"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"" + }"; + + private const string STATIONSALLEMPTY = @"{ + ""copri_version"" : ""4.1.0.0"", + ""stations"" : { + }, + ""user_group"" : [ ""Konrad"", ""TINK"" ], + ""response_state"" : ""OK"", + ""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"", + ""debuglevel"" : ""2"", + ""response"" : ""stations_all"", + ""user_id"" : ""javaminister@gmail.com"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"" + }"; + + [Test] + public async Task TestGetStations_StationsFromCache() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetStations(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(STATIONSALL), + new System.Exception("Bang when getting stations...")))); + + server.Stub(x => x.GetBikesAvailable(Arg.Matches(fromCache => fromCache == true))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESAVAILABLE)))); + + var result = await new CachedQuery(server).GetBikesAndStationsAsync(); + + Assert.AreEqual(3, result.Response.StationsAll.Count); + Assert.AreEqual(1, result.Response.Bikes.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.AreEqual("Bang when getting stations...", result.Exception.Message); + } + + [Test] + public async Task TestGetStations_BikesAvailableFromCache() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetStations(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(STATIONSALLEMPTY)))); + + server.Stub(x => x.GetBikesAvailable(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESAVAILABLE), + new System.Exception("Bang when getting bikes...")))); + + server.Stub(x => x.GetStations(Arg.Matches(fromCache => fromCache == true))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(STATIONSALL)))); + + var result = await new CachedQuery(server).GetBikesAndStationsAsync(); + + Assert.AreEqual(3, result.Response.StationsAll.Count); + Assert.AreEqual(1, result.Response.Bikes.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.AreEqual("Bang when getting bikes...", result.Exception.Message); + } + + + [Test] + public async Task TestGetStations() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetStations(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(STATIONSALL)))); + + server.Stub(x => x.GetBikesAvailable(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(BIKESAVAILABLE)))); + + server.Stub(x => x.AddToCache(Arg>.Is.Anything)); + server.Stub(x => x.AddToCache(Arg>.Is.Anything)); + + var result = await new CachedQuery(server).GetBikesAndStationsAsync(); + + Assert.AreEqual(3, result.Response.StationsAll.Count); + Assert.AreEqual(1, result.Response.Bikes.Count); + Assert.AreEqual(typeof(CopriCallsHttps), result.Source); + Assert.IsNull(result.Exception); + } + + [Test] + public async Task TestGetBikes() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetBikesAvailable()).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(BIKESAVAILABLE)))); + + server.Stub(x => x.AddToCache(Arg>.Is.Anything)); + + var result = await new CachedQuery(server).GetBikesAsync(); + + Assert.AreEqual(1, result.Response.Count); + Assert.AreEqual(typeof(CopriCallsHttps), result.Source); + Assert.IsNull(result.Exception); + } + + [Test] + public async Task TestGetBikesOccupied() + { + var server = MockRepository.GenerateMock(); + + var result = await new CachedQuery(server).GetBikesOccupiedAsync(); + + Assert.AreEqual(0, result.Response.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.AreEqual(result.Exception.Message, "Abfrage der reservierten/ gebuchten Räder nicht möglich. Kein Benutzer angemeldet."); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestCachedQueryLoggedIn.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestCachedQueryLoggedIn.cs new file mode 100644 index 0000000..bc716d0 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestCachedQueryLoggedIn.cs @@ -0,0 +1,338 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using Rhino.Mocks; +using System; +using System.Threading.Tasks; +using TINK.Model.Connector; +using TINK.Model.Services.CopriApi; +using TINK.Repository; +using TINK.Repository.Response; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector.Query +{ + + [TestFixture] + public class TestCachedQueryLoggedIn + { + private const string BIKESAVAILABLE = @"{ + ""copri_version"" : ""4.1.0.0"", + ""bikes"" : {}, + ""response_state"" : ""OK"", + ""apiserver"" : ""https://app.tink-konstanz.de"", + ""authcookie"" : """", + ""response"" : ""bikes_available"", + ""bikes"" : { + ""2352"" : { + ""description"" : ""Cargo Long"", + ""state"" : ""available"", + ""bike"" : ""1"", + ""gps"" : { ""latitude"": ""47.669888"", ""longitude"": ""9.167749"" }, + ""station"" : ""9"" + } + } + }"; + + private const string BIKESAVAILABLEEMPTY = @"{ + ""copri_version"" : ""4.1.0.0"", + ""bikes"" : {}, + ""response_state"" : ""OK"", + ""apiserver"" : ""https://app.tink-konstanz.de"", + ""authcookie"" : """", + ""response"" : ""bikes_available"", + ""bikes"" : { + } + }"; + + private const string BIKESOCCUPIED = @"{ + ""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"", + ""debuglevel"" : ""2"", + ""user_group"" : [ ""TINK"" ], + ""user_id"" : ""javaminister@gmail.com"", + ""response"" : ""user_bikes_occupied"", + ""response_state"" : ""OK"", + ""response_text"" : ""Die Liste der reservierten und gebuchten Fahrräder wurde erfolgreich geladen"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"", + ""bikes_occupied"" : { + ""89004"" : { + ""start_time"" : ""2018-01-27 17:33:00.989464+01"", + ""station"" : ""9"", + ""unit_price"" : ""2.00"", + ""tariff_description"": { + ""free_hours"" : ""0.5"", + ""name"" : ""TINK Tarif"", + ""max_eur_per_day"" : ""9.00"" + }, + ""timeCode"" : ""2061"", + ""description"" : ""Cargo Long"", + ""bike"" : ""4"", + ""total_price"" : ""20.00"", + ""state"" : ""requested"", + ""real_hours"" : ""66.05"", + ""bike_group"" : [ ""TINK"" ], + ""now_time"" : ""2018-01-30 11:36:45"", + ""request_time"" : ""2018-01-27 17:33:00.989464+01"", + ""computed_hours"" : ""10.0"" + } + } + }"; + + private const string STATIONSALL = @"{ + ""copri_version"" : ""4.1.0.0"", + ""stations"" : { + ""5"" : { + ""station"" : ""5"", + ""bike_soll"" : ""0"", + ""bike_ist"" : ""7"", + ""station_group"" : [ ""TINK"" ], + ""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" }, + ""state"" : ""available"", + ""description"" : """" + }, + ""13"" : { + ""station"" : ""13"", + ""bike_soll"" : ""4"", + ""bike_ist"" : ""1"", + ""station_group"" : [ ""TINK"" ], + ""gps"" : { ""latitude"": ""47.657756"", ""longitude"": ""9.176084"" }, + ""state"" : ""available"", + ""description"" : """" + }, + ""30"" : { + ""station"" : ""30"", + ""bike_soll"" : ""5"", + ""bike_ist"" : ""0"", + ""station_group"" : [ ""TINK"", ""Konrad"" ], + ""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" }, + ""state"" : ""available"", + ""description"" : ""Test für Stadtradstation"" + } + }, + ""user_group"" : [ ""Konrad"", ""TINK"" ], + ""response_state"" : ""OK"", + ""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"", + ""debuglevel"" : ""2"", + ""response"" : ""stations_all"", + ""user_id"" : ""javaminister@gmail.com"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"" + }"; + + private const string STATIONSALLEMPTY = @"{ + ""copri_version"" : ""4.1.0.0"", + ""stations"" : { + }, + ""user_group"" : [ ""Konrad"", ""TINK"" ], + ""response_state"" : ""OK"", + ""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"", + ""debuglevel"" : ""2"", + ""response"" : ""stations_all"", + ""user_id"" : ""javaminister@gmail.com"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"" + }"; + + [Test] + public async Task TestGetStations_StationsFromCache() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetStations(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(STATIONSALL), + new System.Exception("Bang when getting stations...")))); + + server.Stub(x => x.GetBikesAvailable(Arg.Matches(fromCache => fromCache == true))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESAVAILABLE)))); + + server.Stub(x => x.GetBikesOccupied(Arg.Matches(fromCache => fromCache == true))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESOCCUPIED)))); + + var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync(); + + Assert.AreEqual(3, result.Response.StationsAll.Count); + Assert.AreEqual(2, result.Response.Bikes.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.AreEqual("Bang when getting stations...", result.Exception.Message); + } + + [Test] + public async Task TestGetStations_BikesAvailableFromCache() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetStations(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(STATIONSALLEMPTY)))); + + server.Stub(x => x.GetBikesAvailable(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESAVAILABLE), + new System.Exception("Bang when getting bikes...")))); + + server.Stub(x => x.GetBikesOccupied(Arg.Matches(fromCache => fromCache == true))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESOCCUPIED)))); + + server.Stub(x => x.GetStations(Arg.Matches(fromCache => fromCache == true))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(STATIONSALL)))); + + var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync(); + + Assert.AreEqual(3, result.Response.StationsAll.Count); + Assert.AreEqual(2, result.Response.Bikes.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.AreEqual("Bang when getting bikes...", result.Exception.Message); + } + + [Test] + public async Task TestGetStations_BikesOccupiedFromCache() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetStations(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(STATIONSALLEMPTY)))); + + server.Stub(x => x.GetBikesAvailable(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(BIKESAVAILABLEEMPTY)))); + + server.Stub(x => x.GetBikesOccupied(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESOCCUPIED), + new System.Exception("Bang when getting bikes occupied...")))); + + server.Stub(x => x.GetBikesAvailable(Arg.Matches(fromCache => fromCache == true))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESAVAILABLE)))); + + server.Stub(x => x.GetStations(Arg.Matches(fromCache => fromCache == true))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(STATIONSALL)))); + + var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync(); + + Assert.AreEqual(3, result.Response.StationsAll.Count); + Assert.AreEqual(2, result.Response.Bikes.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.AreEqual("Bang when getting bikes occupied...", result.Exception.Message); + } + + [Test] + public async Task TestGetStations() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetStations(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(STATIONSALL)))); + + server.Stub(x => x.GetBikesAvailable(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(BIKESAVAILABLE)))); + + server.Stub(x => x.GetBikesOccupied(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(BIKESOCCUPIED)))); + + server.Stub(x => x.AddToCache(Arg>.Is.Anything)); + server.Stub(x => x.AddToCache(Arg>.Is.Anything)); + server.Stub(x => x.AddToCache(Arg>.Is.Anything)); + + var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync(); + + Assert.AreEqual(3, result.Response.StationsAll.Count); + Assert.AreEqual(2, result.Response.Bikes.Count); + Assert.AreEqual(typeof(CopriCallsHttps), result.Source); + Assert.IsNull(result.Exception); + } + + [Test] + public async Task TestGetBikes_BikesAvailableFromCache() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetBikesAvailable()).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESAVAILABLE), + new System.Exception("Bang, bikes avail...")))); + + server.Stub(x => x.GetBikesOccupied(Arg.Matches(fromCache => fromCache == true))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(BIKESOCCUPIED)))); + + var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAsync(); + + Assert.AreEqual(2, result.Response.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.AreEqual("Bang, bikes avail...", result.Exception.Message); + } + + [Test] + public async Task TestGetBikes_BikesOccupiedFromCache() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetBikesAvailable(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(BIKESAVAILABLEEMPTY)))); + + server.Stub(x => x.GetBikesOccupied(Arg.Matches(fromCache => fromCache == false))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESOCCUPIED), + new System.Exception("Bang, error bikes occupied")))); + + server.Stub(x => x.GetBikesAvailable(Arg.Matches(fromCache => fromCache == true))).Return(Task.Run(() => new Result( + typeof(CopriCallsMonkeyStore), + JsonConvert.DeserializeObject(BIKESAVAILABLE)))); + + var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAsync(); + + Assert.AreEqual(2, result.Response.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.AreEqual("Bang, error bikes occupied", result.Exception.Message); + } + + [Test] + public async Task TestGetBikes() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetBikesAvailable()).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(BIKESAVAILABLE)))); + + server.Stub(x => x.GetBikesOccupied()).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(BIKESOCCUPIED)))); + + server.Stub(x => x.AddToCache(Arg>.Is.Anything)); + server.Stub(x => x.AddToCache(Arg>.Is.Anything)); + + var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAsync(); + + Assert.AreEqual(2, result.Response.Count); + Assert.AreEqual(typeof(CopriCallsHttps), result.Source); + Assert.IsNull(result.Exception); + } + + [Test] + public async Task TestGetBikesOccupied() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetBikesOccupied()).Return(Task.Run(() => new Result( + typeof(CopriCallsHttps), + JsonConvert.DeserializeObject(BIKESOCCUPIED)))); + + server.Stub(x => x.AddToCache(Arg>.Is.Anything)); + + var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesOccupiedAsync(); + + Assert.AreEqual(1, result.Response.Count); + Assert.AreEqual(typeof(CopriCallsHttps), result.Source); + Assert.IsNull(result.Exception); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestQuery.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestQuery.cs new file mode 100644 index 0000000..05dbe8d --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestQuery.cs @@ -0,0 +1,113 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using Rhino.Mocks; +using System.Threading.Tasks; +using TINK.Repository; +using TINK.Repository.Response; + +namespace TestTINKLib.Fixtures.ObjectTests.Query +{ + [TestFixture] + public class TestQuery + { + private const string BIKESAVAILABLE = @"{ + ""copri_version"" : ""4.1.0.0"", + ""bikes"" : {}, + ""response_state"" : ""OK"", + ""apiserver"" : ""https://app.tink-konstanz.de"", + ""authcookie"" : """", + ""response"" : ""bikes_available"", + ""bikes"" : { + ""2352"" : { + ""description"" : ""Cargo Long"", + ""state"" : ""available"", + ""bike"" : ""1"", + ""gps"" : { ""latitude"": ""47.669888"", ""longitude"": ""9.167749"" }, + ""station"" : ""9"" + } + } + }"; + + private const string STATIONSALL = @"{ + ""copri_version"" : ""4.1.0.0"", + ""stations"" : { + ""5"" : { + ""station"" : ""5"", + ""bike_soll"" : ""0"", + ""bike_ist"" : ""7"", + ""station_group"" : [ ""TINK"" ], + ""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" }, + ""state"" : ""available"", + ""description"" : """" + }, + ""13"" : { + ""station"" : ""13"", + ""bike_soll"" : ""4"", + ""bike_ist"" : ""1"", + ""station_group"" : [ ""TINK"" ], + ""gps"" : { ""latitude"": ""47.657756"", ""longitude"": ""9.176084"" }, + ""state"" : ""available"", + ""description"" : """" + }, + ""30"" : { + ""station"" : ""30"", + ""bike_soll"" : ""5"", + ""bike_ist"" : ""0"", + ""station_group"" : [ ""TINK"", ""Konrad"" ], + ""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" }, + ""state"" : ""available"", + ""description"" : ""Test für Stadtradstation"" + } + }, + ""user_group"" : [ ""Konrad"", ""TINK"" ], + ""response_state"" : ""OK"", + ""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"", + ""debuglevel"" : ""2"", + ""response"" : ""stations_all"", + ""user_id"" : ""javaminister@gmail.com"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"" + }"; + + [Test] + public async Task TestGetStations() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetStationsAsync()).Return(Task.Run(() => JsonConvert.DeserializeObject(STATIONSALL))); + server.Stub(x => x.GetBikesAvailableAsync()).Return(Task.Run(() => JsonConvert.DeserializeObject(BIKESAVAILABLE))); + + var result = await new TINK.Model.Connector.Query(server).GetBikesAndStationsAsync(); + + Assert.AreEqual(3, result.Response.StationsAll.Count); + Assert.AreEqual(1, result.Response.Bikes.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.IsNull(result.Exception); + } + + [Test] + public async Task TestGetBikes() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetBikesAvailableAsync()).Return(Task.Run(() => JsonConvert.DeserializeObject(BIKESAVAILABLE))); + + var result = await new TINK.Model.Connector.Query(server).GetBikesAsync(); + + Assert.AreEqual(1, result.Response.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.IsNull(result.Exception); + } + + [Test] + public async Task TestGetBikesOccupied() + { + var server = MockRepository.GenerateMock(); + + var result = await new TINK.Model.Connector.Query(server).GetBikesOccupiedAsync(); + + Assert.AreEqual(0, result.Response.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.AreEqual(result.Exception.Message, "Abfrage der reservierten/ gebuchten Räder fehlgeschlagen. Kein Benutzer angemeldet."); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestQueryLoggedIn.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestQueryLoggedIn.cs new file mode 100644 index 0000000..69c0046 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Query/TestQueryLoggedIn.cs @@ -0,0 +1,152 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using Rhino.Mocks; +using System; +using System.Threading.Tasks; +using TINK.Model.Connector; +using TINK.Repository.Response; +using TINK.Repository; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector +{ + [TestFixture] + public class TestQueryLoggedIn + { + private const string BIKESAVAILABLE = @"{ + ""copri_version"" : ""4.1.0.0"", + ""bikes"" : {}, + ""response_state"" : ""OK"", + ""apiserver"" : ""https://app.tink-konstanz.de"", + ""authcookie"" : """", + ""response"" : ""bikes_available"", + ""bikes"" : { + ""2352"" : { + ""description"" : ""Cargo Long"", + ""state"" : ""available"", + ""bike"" : ""1"", + ""gps"" : { ""latitude"": ""47.669888"", ""longitude"": ""9.167749"" }, + ""station"" : ""9"" + } + } + }"; + + private const string BIKESOCCUPIED = @"{ + ""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"", + ""debuglevel"" : ""2"", + ""user_group"" : [ ""TINK"" ], + ""user_id"" : ""javaminister@gmail.com"", + ""response"" : ""user_bikes_occupied"", + ""response_state"" : ""OK"", + ""response_text"" : ""Die Liste der reservierten und gebuchten Fahrräder wurde erfolgreich geladen"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"", + ""bikes_occupied"" : { + ""89004"" : { + ""start_time"" : ""2018-01-27 17:33:00.989464+01"", + ""station"" : ""9"", + ""unit_price"" : ""2.00"", + ""tariff_description"": { + ""free_hours"" : ""0.5"", + ""name"" : ""TINK Tarif"", + ""max_eur_per_day"" : ""9.00"" + }, + ""timeCode"" : ""2061"", + ""description"" : ""Cargo Long"", + ""bike"" : ""4"", + ""total_price"" : ""20.00"", + ""state"" : ""requested"", + ""real_hours"" : ""66.05"", + ""bike_group"" : [ ""TINK"" ], + ""now_time"" : ""2018-01-30 11:36:45"", + ""request_time"" : ""2018-01-27 17:33:00.989464+01"", + ""computed_hours"" : ""10.0"" + } + } + }"; + + private const string STATIONSALL = @"{ + ""copri_version"" : ""4.1.0.0"", + ""stations"" : { + ""5"" : { + ""station"" : ""5"", + ""bike_soll"" : ""0"", + ""bike_ist"" : ""7"", + ""station_group"" : [ ""TINK"" ], + ""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" }, + ""state"" : ""available"", + ""description"" : """" + }, + ""13"" : { + ""station"" : ""13"", + ""bike_soll"" : ""4"", + ""bike_ist"" : ""1"", + ""station_group"" : [ ""TINK"" ], + ""gps"" : { ""latitude"": ""47.657756"", ""longitude"": ""9.176084"" }, + ""state"" : ""available"", + ""description"" : """" + }, + ""30"" : { + ""station"" : ""30"", + ""bike_soll"" : ""5"", + ""bike_ist"" : ""0"", + ""station_group"" : [ ""TINK"", ""Konrad"" ], + ""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" }, + ""state"" : ""available"", + ""description"" : ""Test für Stadtradstation"" + } + }, + ""user_group"" : [ ""Konrad"", ""TINK"" ], + ""response_state"" : ""OK"", + ""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"", + ""debuglevel"" : ""2"", + ""response"" : ""stations_all"", + ""user_id"" : ""javaminister@gmail.com"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"" + }"; + + [Test] + public async Task TestGetStations() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetStationsAsync()).Return(Task.Run(() => JsonConvert.DeserializeObject(STATIONSALL))); + server.Stub(x => x.GetBikesAvailableAsync()).Return(Task.Run(() => JsonConvert.DeserializeObject(BIKESAVAILABLE))); + server.Stub(x => x.GetBikesOccupiedAsync()).Return(Task.Run(() => JsonConvert.DeserializeObject(BIKESOCCUPIED))); + + var result = await new QueryLoggedIn(server, "123", "a@b", ()=> DateTime.Now).GetBikesAndStationsAsync(); + + Assert.AreEqual(3, result.Response.StationsAll.Count); + Assert.AreEqual(2, result.Response.Bikes.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.IsNull(result.Exception); + } + + [Test] + public async Task TestGetBikes() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetBikesAvailableAsync()).Return(Task.Run(() => JsonConvert.DeserializeObject(BIKESAVAILABLE))); + server.Stub(x => x.GetBikesOccupiedAsync()).Return(Task.Run(() => JsonConvert.DeserializeObject(BIKESOCCUPIED))); + + var result = await new QueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAsync(); + + Assert.AreEqual(2, result.Response.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.IsNull(result.Exception); + } + + [Test] + public async Task TestGetBikesOccupied() + { + var server = MockRepository.GenerateMock(); + + server.Stub(x => x.GetBikesOccupiedAsync()).Return(Task.Run(() => JsonConvert.DeserializeObject(BIKESOCCUPIED))); + + var result = await new QueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesOccupiedAsync(); + + Assert.AreEqual(1, result.Response.Count); + Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); + Assert.IsNull(result.Exception); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Request/TestRequestBuilder.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Request/TestRequestBuilder.cs new file mode 100644 index 0000000..0ca652a --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Request/TestRequestBuilder.cs @@ -0,0 +1,70 @@ +using NUnit.Framework; +using System; +using TINK.Repository.Exception; +using TINK.Repository.Request; + +namespace UITest.Fixtures.ObjectTests.Connector.Request +{ + [TestFixture] + public class TestRequestBuilder + { + [Test] + public void TestDoAuthorize() + { + Assert.AreEqual( + "request=authorization&merchant_id=123&user_id=abc%40cde&user_pw=%2B%3F&hw_id=789", + new RequestBuilder("123").DoAuthorization("abc@cde", "+?", "789")); + } + + [Test] + public void TestDoAuthout() + { + Assert.Throws(() => + new RequestBuilder("123").DoAuthout()); + } + + [Test] + public void TestGetBikesAvailable() + { + Assert.AreEqual( + "request=bikes_available&system=all&authcookie=123", + new RequestBuilder("123").GetBikesAvailable()); + } + + [Test] + public void TestGetBikesOccupied() + { + Assert.Throws< NotSupportedException>(() => + new RequestBuilder("123").GetBikesOccupied()); + } + + [Test] + public void TestGetStations() + { + Assert.AreEqual( + "request=stations_available&authcookie=123", + new RequestBuilder("123").GetStations()); + } + + [Test] + public void TestDoReserve() + { + Assert.Throws(() => + new RequestBuilder("123").DoReserve("42")); + } + + [Test] + public void TestDoCancelReservation() + { + Assert.Throws(() => + new RequestBuilder("123").DoCancelReservation("42")); + } + + [Test] + public void TestDoSubmitFeedback() + { + Assert.Throws(() => + new RequestBuilder("123").DoSubmitFeedback("bike3", "Hi", false)); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Request/TestRequestBuilderLoggedIn.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Request/TestRequestBuilderLoggedIn.cs new file mode 100644 index 0000000..5f52296 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Request/TestRequestBuilderLoggedIn.cs @@ -0,0 +1,112 @@ +using NUnit.Framework; +using System; +using TINK.Repository.Exception; +using TINK.Repository.Request; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector.Request +{ + [TestFixture] + public class TestRequestBuilderLoggedIn + { + + [Test] + public void TestDoAuthorize() + { + Assert.Throws (() =>new RequestBuilderLoggedIn("123", "456").DoAuthorization("abc@cde", "+?", "789")); + } + + [Test] + public void TestDoAuthout() + { + Assert.AreEqual( + "request=authout&authcookie=456123", + new RequestBuilderLoggedIn ("123", "456").DoAuthout()); + } + + [Test] + public void TestGetBikesAvailable() + { + Assert.AreEqual( + "request=bikes_available&system=all&authcookie=456123", + new RequestBuilderLoggedIn("123", "456").GetBikesAvailable()); + } + + [Test] + public void TestGetBikesAvailable_Null() + { + Assert.Throws(() => + new RequestBuilderLoggedIn("123", null).GetBikesAvailable()); + + Assert.Throws(() => + new RequestBuilderLoggedIn("123", string.Empty).GetBikesAvailable()); + } + + [Test] + public void TestGetBikesOccupied() + { + Assert.AreEqual( + "request=user_bikes_occupied&system=all&genkey=1&authcookie=456123", + new RequestBuilderLoggedIn("123", "456").GetBikesOccupied()); + } + + [Test] + public void TestGetBikesOccupied_Null() + { + Assert.Throws(() => + new RequestBuilderLoggedIn("123", null).GetBikesOccupied()); + + Assert.Throws(() => + new RequestBuilderLoggedIn("123", "").GetBikesOccupied()); + } + + [Test] + public void TestGetStations() + { + Assert.AreEqual( + "request=stations_available&authcookie=456123", + new RequestBuilderLoggedIn("123", "456").GetStations()); + } + + [Test] + public void TestGetStations_Null() + { + Assert.Throws(() => + new RequestBuilderLoggedIn("123", string.Empty).GetStations()); + + Assert.Throws(() => + new RequestBuilderLoggedIn("123", null).GetStations()); + } + + [Test] + public void TestDoReserve() + { + Assert.AreEqual( + "request=booking_request&bike=42&authcookie=456123", + new RequestBuilderLoggedIn("123", "456").DoReserve("42")); + } + + [Test] + public void TestDoCancelReservation() + { + Assert.AreEqual( + "request=booking_cancel&bike=42&authcookie=456123", + new RequestBuilderLoggedIn("123", "456").DoCancelReservation("42")); + } + + [Test] + public void TestDoBook() + { + Assert.AreEqual( + "request=booking_update&bike=42&authcookie=456123&Ilockit_GUID=0000f00d-1212-efde-1523-785fef13d123&state=occupied&lock_state=unlocked&voltage=33.21", + new RequestBuilderLoggedIn("123", "456").DoBook("42", new Guid("0000f00d-1212-efde-1523-785fef13d123"),33.21)); + } + + [Test] + public void TestDoBookNoBatery() + { + Assert.AreEqual( + "request=booking_update&bike=42&authcookie=456123&Ilockit_GUID=0000f00d-1212-efde-1523-785fef13d123&state=occupied&lock_state=unlocked", + new RequestBuilderLoggedIn("123", "456").DoBook("42", new Guid("0000f00d-1212-efde-1523-785fef13d123"), double.NaN)); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestBikesAvailableResponse.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestBikesAvailableResponse.cs new file mode 100644 index 0000000..17fbe3f --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestBikesAvailableResponse.cs @@ -0,0 +1,29 @@ +using NUnit.Framework; +using TINK.Model; +using TINK.Repository; + +using static TINK.Repository.CopriCallsMemory; + +namespace TestTINKLib.Fixtures.Connector +{ + + [TestFixture] + public class TestBikesAvailableResponse + { + [Test] + public void TestDeserialize_StateAvailable() + { + // Deserialize object and verify. + var l_oContainer = GetBikesAvailable(TinkApp.MerchantId, p_eSampleSet: SampleSets.Set2, p_lStageIndex: 1); + Assert.AreEqual(12, l_oContainer.bikes.Count); + + // Check first entry. + Assert.AreEqual("Cargo Trike", l_oContainer.bikes["3399"].description); + Assert.AreEqual("26", l_oContainer.bikes["3399"].bike); + Assert.AreEqual("available", l_oContainer.bikes["3399"].state); + Assert.AreEqual("47.6586936667", l_oContainer.bikes["3399"].gps.latitude); + Assert.AreEqual("9.16863116667", l_oContainer.bikes["3399"].gps.longitude); + Assert.AreEqual("4", l_oContainer.bikes["3399"].station); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestBikesOccupiedResponse.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestBikesOccupiedResponse.cs new file mode 100644 index 0000000..9e4996e --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestBikesOccupiedResponse.cs @@ -0,0 +1,51 @@ +using NUnit.Framework; +using TINK.Repository; + +using static TINK.Repository.CopriCallsMemory; + +namespace TestTINKLib.Fixtures.Connector.Response +{ + + [TestFixture] + public class TestBikesOccupiedResponse + { + [Test] + public void TestDeserialize() + { + // Deserialize object and verify. + var l_oContainer = GetBikesOccupied("4da3044c8657a04ba60e2eaa753bc51a", SampleSets.Set2, 1); + + Assert.AreEqual(2, l_oContainer.bikes_occupied.Count); + + // Check first entry. + Assert.AreEqual("3630", l_oContainer.bikes_occupied["87781"].timeCode); + Assert.AreEqual("occupied", l_oContainer.bikes_occupied["87781"].state); + Assert.AreEqual("5", l_oContainer.bikes_occupied["87781"].station); + Assert.AreEqual("Cargo Long", l_oContainer.bikes_occupied["87781"].description); + Assert.AreEqual("2017-11-28 11:01:51.637747+01", l_oContainer.bikes_occupied["87781"].start_time); + Assert.AreEqual("8", l_oContainer.bikes_occupied["87781"].bike); + + // Check first entry. + Assert.AreEqual("2931", l_oContainer.bikes_occupied["87782"].timeCode); + Assert.AreEqual("occupied", l_oContainer.bikes_occupied["87782"].state); + Assert.AreEqual("4", l_oContainer.bikes_occupied["87782"].station); + Assert.AreEqual("Cargo Long", l_oContainer.bikes_occupied["87782"].description); + Assert.AreEqual("2017-11-28 13:06:55.147368+01", l_oContainer.bikes_occupied["87782"].start_time); + Assert.AreEqual("7", l_oContainer.bikes_occupied["87782"].bike); + } + + [Test] + public void TestDeserialize_StateReserved() + { + // Deserialize object and verify. + var l_oContainer = CopriCallsMemory.GetBikesOccupied("4da3044c8657a04ba60e2eaa753bc51a", SampleSets.Set2, 2); + Assert.AreEqual(3, l_oContainer.bikes_occupied.Count); + + // Check first entry. + Assert.AreEqual("Cargo Long", l_oContainer.bikes_occupied["2360"].description); + Assert.AreEqual("5", l_oContainer.bikes_occupied["2360"].bike); + Assert.AreEqual("reserved", l_oContainer.bikes_occupied["2360"].state); + Assert.AreEqual("4", l_oContainer.bikes_occupied["2360"].station); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestBookingResponse.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestBookingResponse.cs new file mode 100644 index 0000000..3c97274 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestBookingResponse.cs @@ -0,0 +1,54 @@ +using NUnit.Framework; +using TINK.Repository; +using TINK.Repository.Response; +using static TINK.Repository.CopriCallsMemory; + +namespace TestTINKLib.Fixtures.Connector.Response +{ + + [TestFixture] + public class TestBookingResponse + { + [Test] + public void TestDeserialize() + { + // Deserialize object and verify. + var l_oContainer = CopriCallsMemory.DoReserve("8", "b76b97e43a2d76b8499f32e6dd597af8", SampleSets.Set2, 1); + + Assert.AreEqual(2, l_oContainer.bikes_occupied.Count); + Assert.AreEqual("3630", l_oContainer.timeCode); + Assert.AreEqual("OK: requested bike 8", l_oContainer.response_state); + + // Check first entry which is bike #8 + Assert.AreEqual("3630", l_oContainer.bikes_occupied["87781"].timeCode); + Assert.AreEqual("occupied", l_oContainer.bikes_occupied["87781"].state); + Assert.AreEqual("5", l_oContainer.bikes_occupied["87781"].station); + Assert.AreEqual("Cargo Long", l_oContainer.bikes_occupied["87781"].description); + Assert.AreEqual("2017-11-28 11:01:51.637747+01", l_oContainer.bikes_occupied["87781"].start_time); + Assert.AreEqual("8", l_oContainer.bikes_occupied["87781"].bike); + } + + [Test] + public void TestGetIsBookingResponseSucceeded() + { + // Create response to check + var l_oResponse = DoReserve( + "8", + "4da3044c8657a04ba60e2eaa753bc51a", + SampleSets.Set2, + 1); + + Assert.AreEqual( + "4da3044c8657a04ba60e2eaa753bc51aoiF2kahH", + l_oResponse.authcookie); + + Assert.AreEqual( + "OK: requested bike 8", + l_oResponse.response_state); + + Assert.NotNull( + l_oResponse.GetIsReserveResponseOk("8"), + "Booking did succeed, response must not be null."); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestResponseBase.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestResponseBase.cs new file mode 100644 index 0000000..1db087c --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestResponseBase.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; +using NUnit.Framework; + + +namespace TINK.Repository.Response +{ + + [TestFixture] + public class TestResponseBase + { + [Test] + public void TestDeserialize() + { + // Deserialize object and verify. + var l_oContainer = CopriCallsMemory.DoAuthorize("javaminister@gmail.com", "javaminister", "HwId1000000000000"); + + // Check first entry. + Assert.AreEqual("authorization", l_oContainer.response); + Assert.AreEqual("4da3044c8657a04ba60e2eaa753bc51a", l_oContainer.authcookie); + Assert.AreEqual("OK", l_oContainer.response_state); + } + + [Test] + public void TestToString() + { + var l_oResponse = JsonConvert.DeserializeObject(@" + { + ""response_state"": ""OhMyState"", + ""response"": ""HabGsagt"", + ""response_text"": ""die Antwort"", + ""authcookie"": ""lecker1"", + ""copri_version"":""123"" + }"); + + Assert.AreEqual( + "Response state is \"OhMyState\", " + + $"auth cookie is \"lecker1\" and response is \"die Antwort\", " + + $"code \"HabGsagt\""+ + $"response text \"die Antwort\".", + l_oResponse.ToString()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestResponseHelper.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestResponseHelper.cs new file mode 100644 index 0000000..1e6cd0a --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestResponseHelper.cs @@ -0,0 +1,86 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using TINK.Repository.Exception; +using TINK.Repository.Response; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector.Response +{ + + [TestFixture] + public class TestResponseHelper + { + [Test] + public void TestGetIsResponseOk_BikesOccupied_Ok() + { + var l_oResponse = JsonConvert.DeserializeObject(@"{ ""response_state"" : ""OK"" }"); + Assert.NotNull(l_oResponse.GetIsResponseOk(ResponseHelper.BIKES_OCCUPIED_ACTIONTEXT)); + } + + [Test] + public void TestGetIsResponseOk_BikesOccupied_AuthcookieNotDefined() + { + var l_oResponseBase = JsonConvert.DeserializeObject($"{{ \"response_state\" : \"Failure 1003: authcookie not defined\" }}"); + Assert.Throws(() => l_oResponseBase.GetIsResponseOk("Get not succeed")); + } + + + [Test] + public void TestGetIsResponseOk_NoBikes() + { + var l_oResponse = JsonConvert.DeserializeObject( + @"{ ""response_state"" : ""OK"", " + + @"""authcookie"" : ""KeksoiF2kahH"" }"); + + Assert.That(() => l_oResponse.GetIsReserveResponseOk("8"), Throws.Exception.TypeOf()); + } + + [Test] + public void TestGetIsResposeOk_Booking_Declined() + { + var l_oResponse = JsonConvert.DeserializeObject(@"{ ""response_state"" : ""OK: booking_request declined. max count of 8 occupied bikes has been reached"", ""authcookie"" : ""KeksoiF2kahH"" }"); + Assert.AreEqual( + 8, + Assert.Throws(() => l_oResponse.GetIsReserveResponseOk("8")).MaxBikesCount); + } + + [Test] + public void TestGetIsResposeOk_Logout_AutcookieUnknown() + { + var l_oResponse = JsonConvert.DeserializeObject($"{{ \"response_state\" : \"Failure 1004: authcookie not defined\"}}"); + + Assert.Throws(() => l_oResponse.GetIsResponseOk()); + } + + [Test] + public void TestGetIsReturnBikeResponseOk_Error() + { + var l_oResponse = JsonConvert.DeserializeObject( + @"{ ""response_state"" : ""Failure 1234"", " + + @"""authcookie"" : ""KeksoiF2kahH"" }"); + + Assert.That( + () => l_oResponse.GetIsReturnBikeResponseOk("8"), + Throws.Exception.TypeOf>()); + } + + [Test] + public void TestGetIsReturnBikeResponseOk_ErrorNotAtStation() + { + var l_oResponse = JsonConvert.DeserializeObject( + @"{ ""response_state"" : ""Failure 2178: bike 1545 out of GEO fencing. 15986 meter distance to next station 66. OK: bike 1545 locked confirmed"", " + + @"""authcookie"" : ""KeksoiF2kahH"" }"); + + Assert.That(() => l_oResponse.GetIsReturnBikeResponseOk("8"), Throws.Exception.TypeOf()); + } + + [Test] + public void TestGetIsReturnBikeResponseOk_ErrorNoGPSData() + { + var l_oResponse = JsonConvert.DeserializeObject( + @"{ ""response_state"" : ""Failure 2245: No GPS data, state change forbidden."", " + + @"""authcookie"" : ""KeksoiF2kahH"" }"); + + Assert.That(() => l_oResponse.GetIsReturnBikeResponseOk("8"), Throws.Exception.TypeOf()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestStationsAllResponse.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestStationsAllResponse.cs new file mode 100644 index 0000000..d8ff658 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/Response/TestStationsAllResponse.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; +using System.Linq; +using TINK.Model; + +using static TINK.Repository.CopriCallsMemory; + +namespace TestTINKLib.Fixtures.Connector +{ + + [TestFixture] + public class TestStationsAllResponse + { + [Test] + public void TestDeserialize() + { + // Deserialize object and verify. + var l_oContainer = GetStationsAll(TinkApp.MerchantId, p_eSampleSet: SampleSets.Set2, p_lStageIndex: 1); + Assert.AreEqual(9, l_oContainer.stations.Count); + + // Check first entry (type TINK). + Assert.AreEqual("4", l_oContainer.stations["5786"].station); + Assert.AreEqual("TINK", string.Join(",", l_oContainer.stations["5786"].station_group)); + Assert.IsNull(l_oContainer.stations["5786"].description); + Assert.AreEqual("47.6586936667", l_oContainer.stations["5786"].gps.latitude); + Assert.AreEqual("9.16863116667", l_oContainer.stations["5786"].gps.longitude); + + // Check Konrad entry. + Assert.AreEqual("14", l_oContainer.stations["14"].station); + Assert.AreEqual("Konrad", string.Join(",", l_oContainer.stations["14"].station_group)); + Assert.AreEqual(string.Empty, l_oContainer.stations["14"].description); + Assert.AreEqual("47.66698054007847", l_oContainer.stations["14"].gps.latitude); + Assert.AreEqual("9.169303178787231", l_oContainer.stations["14"].gps.longitude); + + // Check TINK/ Konrad entry. + Assert.AreEqual("31", l_oContainer.stations["31"].station); + Assert.AreEqual("TINK,Konrad", string.Join(",", l_oContainer.stations["31"].station_group)); + Assert.AreEqual("Südstadt Station", l_oContainer.stations["31"].description); + Assert.AreEqual("47.69489", l_oContainer.stations["31"].gps.latitude); + Assert.AreEqual("9.19", l_oContainer.stations["31"].gps.longitude); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/TestCommandLoggedIn.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/TestCommandLoggedIn.cs new file mode 100644 index 0000000..d1b1a1d --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/TestCommandLoggedIn.cs @@ -0,0 +1,73 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using Rhino.Mocks; +using System; +using System.Threading.Tasks; +using TINK.Model.Connector; +using TINK.Repository; +using TINK.Repository.Exception; +using TINK.Repository.Response; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector +{ + [TestFixture] + public class TestCommandLoggedIn + { + /// Verifies, that logout leads to expected call on copri server. + [Test] + public void TestDoLogout() + { + var l_oServer = MockRepository.GenerateStub(); + + l_oServer.Stub(x => x.DoAuthoutAsync()).Return(Task.Run(() => JsonConvert.DeserializeObject("{ \"response_state\" : \"OK\", \"authcookie\" : \"1\"}"))); + + var l_oCmd = new CommandLoggedIn(l_oServer, "MeinKeks", "EMehl", () => DateTime.Now); + + LoginStateChangedEventArgs l_oEventArgs = null; + l_oCmd.LoginStateChanged += (sender, eventargs) => l_oEventArgs = eventargs; + + l_oCmd.DoLogout().Wait(); + + l_oServer.AssertWasCalled(x => x.DoAuthoutAsync()); + Assert.IsNotNull(l_oEventArgs); + } + + /// Verifies, that logout leads to expected call on copri server. + [Test] + public void TestDoLogout_AuthcookieNotDefined() + { + var l_oServer = MockRepository.GenerateStub(); + + l_oServer.Stub(x => x.DoAuthoutAsync()).Throw(new AuthcookieNotDefinedException("Testing action", JsonConvert.DeserializeObject(@"{ ""response_state"" : ""Some inner error description""}"))); + + var l_oCmd = new CommandLoggedIn(l_oServer, "MeinKeks", "EMehl", () => DateTime.Now); + + LoginStateChangedEventArgs l_oEventArgs = null; + l_oCmd.LoginStateChanged += (sender, eventargs) => l_oEventArgs = eventargs; + + l_oCmd.DoLogout().Wait(); + + l_oServer.AssertWasCalled(x => x.DoAuthoutAsync()); + Assert.IsNotNull(l_oEventArgs); + } + + /// Verifies, that logout leads to expected call on copri server. + [Test] + public void TestDoLogout_Exception() + { + var l_oServer = MockRepository.GenerateStub(); + + l_oServer.Stub(x => x.DoAuthoutAsync()).Throw(new System.Exception("Sometheing went wrong.")); + + var l_oCmd = new CommandLoggedIn(l_oServer, "MeinKeks", "EMehl", () => DateTime.Now); + + LoginStateChangedEventArgs l_oEventArgs = null; + l_oCmd.LoginStateChanged += (sender, eventargs) => l_oEventArgs = eventargs; + + Assert.Throws(() => l_oCmd.DoLogout().Wait()); + + l_oServer.AssertWasCalled(x => x.DoAuthoutAsync()); + Assert.IsNull(l_oEventArgs); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/TestConnector.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/TestConnector.cs new file mode 100644 index 0000000..1bcf0b4 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/TestConnector.cs @@ -0,0 +1,84 @@ +using NUnit.Framework; +using Rhino.Mocks; +using TINK.Model.Connector; +using TINK.Model.Services.CopriApi; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector +{ + [TestFixture] + public class TestConnector + { + /// + /// Verifies that factory method returns correcty type depending on session cookie. + /// + [Test] + public void TestCommandFactory() + { + var l_oCopri = MockRepository.GenerateStub(); + + // Construct not logged in version of connector. + var l_oCommand = new TINK.Model.Connector.Connector( + new System.Uri("http://1.2.3.4"), + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default automated test envirnoment", + "", // Not logged in + "", + server: l_oCopri).Command; + + Assert.AreEqual(typeof(Command), l_oCommand.GetType()); + } + + /// + /// Verifies that factory method returns correcty type depending on session cookie. + /// + [Test] + public void TestCommandFactory_LoggedIn() + { + var l_oCopri = MockRepository.GenerateStub(); + + var l_oCommand = new TINK.Model.Connector.Connector( + new System.Uri("http://1.2.3.4"), + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default automated test envirnoment", + "123", // Logged in + "a@b", + server: l_oCopri).Command; + + Assert.AreEqual(typeof(CommandLoggedIn), l_oCommand.GetType()); + } + + /// + /// Verifies that factory method returns correcty type depending on session cookie. + /// + [Test] + public void TestQueryFactory_CachedServer() + { + var l_oCopri = MockRepository.GenerateStub(); + + var l_oQuery = new TINK.Model.Connector.Connector( + new System.Uri("http://1.2.3.4"), + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default automated test envirnoment", + "", + "", + server: l_oCopri).Query; + + Assert.AreEqual(typeof(CachedQuery), l_oQuery.GetType()); + } + + /// + /// Verifies that factory method returns correcty type depending on session cookie. + /// + [Test] + public void TestQueryFactory_LoggedIn() + { + var l_oCopri = MockRepository.GenerateStub(); + + var l_oQuery = new TINK.Model.Connector.Connector( + new System.Uri("http://1.2.3.4"), + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default automated test envirnoment", + "123", + "a@b", + server: l_oCopri).Query; + + Assert.AreEqual(typeof(CachedQueryLoggedIn), l_oQuery.GetType()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/TestConnectorCache.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/TestConnectorCache.cs new file mode 100644 index 0000000..51ab920 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/TestConnectorCache.cs @@ -0,0 +1,76 @@ +using NUnit.Framework; +using Rhino.Mocks; +using TINK.Model.Connector; +using TINK.Repository; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector +{ + [TestFixture] + public class TestConnectorCache + { + /// + /// Verifies that factory method returns correcty type depending on session cookie. + /// + [Test] + public void TestCommandFactory() + { + var l_oCopri = MockRepository.GenerateStub(); + + // Construct not logged in version of connector. + var l_oCommand = new ConnectorCache( + "", // Not logged in + "", + l_oCopri).Command; + + Assert.AreEqual(typeof(Command), l_oCommand.GetType()); + } + + /// + /// Verifies that factory method returns correcty type depending on session cookie. + /// + [Test] + public void TestCommandFactory_LoggedIn() + { + var l_oCopri = MockRepository.GenerateStub(); + + var l_oCommand = new ConnectorCache( + "123", // Logged in + "a@b", + l_oCopri).Command; + + Assert.AreEqual(typeof(CommandLoggedIn), l_oCommand.GetType()); + } + + /// + /// Verifies that factory method returns correcty type depending on session cookie. + /// + [Test] + public void TestQueryFactory_CachedServer() + { + var l_oCopri = MockRepository.GenerateStub(); + + var l_oQuery = new ConnectorCache( + "", + "", + l_oCopri).Query; + + Assert.AreEqual(typeof(TINK.Model.Connector.Query), l_oQuery.GetType()); + } + + /// + /// Verifies that factory method returns correcty type depending on session cookie. + /// + [Test] + public void TestQueryFactory_LoggedIn() + { + var l_oCopri = MockRepository.GenerateStub(); + + var l_oQuery = new ConnectorCache( + "123", + "a@b", + l_oCopri).Query; + + Assert.AreEqual(typeof(QueryLoggedIn), l_oQuery.GetType()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriCallsHttps.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriCallsHttps.cs new file mode 100644 index 0000000..8b44807 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriCallsHttps.cs @@ -0,0 +1,688 @@ +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading; +using TINK.Model; +using TINK.Repository; +using TINK.Repository.Exception; +using TINK.Repository.Response; +using TINK.Repository.Request; + +using static TINK.Repository.CopriCallsHttps; +using TINK.Model.Services.CopriApi.ServerUris; +using System.Reflection; + +namespace UITest.Fixtures.Connector +{ + [TestFixture] + public class TestCopriCallsHttps + { + public const string CATEGORY_REQUIRESCOPRI = "RequiresCOPRI"; + + public const string CATEGORY_USESLIVESERVER = "RequiresCOPRI.Live"; + + public const string CATEGORY_USESDEVELSERVER = "RequiresCOPRI.Devel"; + + public const string TESTAGENT = "TestShareeLib"; + + [TearDown] + public void TearDown() + { + Thread.Sleep(2000); // Sleep, otherwise copri will block requested calls. + } + + [Test] + public void TestIsConnected() + { + Assert.IsTrue(new CopriCallsHttps(new Uri("http://127.0.0.0/api"), "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", "123").IsConnected); + } + + [Test] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#elif !NODEVELSERVER + [Category(CATEGORY_USESDEVELSERVER)] +#endif + public void TestLogin( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] + string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + Func command = (password) => new RequestBuilder(TinkApp.MerchantId).DoAuthorization( + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Mail, + password, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.DeviceId); + + var l_oSessionCookieJM_Dev1 = DoAuthorizationAsync( + url, + command(TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Pwd), + () => command("*******"), + $"{Assembly.GetAssembly(GetType()).GetName().Name}-{GetType().Name}-{nameof(TestLogin)}").Result; + + Assert.AreEqual( + string.Format("{0}{1}", "6103_4da3044c8657a04ba60e2eaa753bc51a_", "oiF2kahH"), + l_oSessionCookieJM_Dev1.authcookie, + "Session cookie must never change if user and hardware id does not change."); + } + + [Test] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#elif !NODEVELSERVER + [Category(CATEGORY_USESDEVELSERVER)] +#endif + public void TestLogin_UserUnknown( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] + string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + Func command = (password) => new RequestBuilder(TinkApp.MerchantId).DoAuthorization( + TestTINKLib.LoginSessionCopriInfo.JavaministerGibtsNet.Mail, + password, + TestTINKLib.LoginSessionCopriInfo.JavaministerGibtsNet.DeviceId); + + var l_oSessionCookieJM_Dev1 = DoAuthorizationAsync( + url, + command(TestTINKLib.LoginSessionCopriInfo.JavaministerGibtsNet.Pwd), + () => command("***********"), + $"{Assembly.GetAssembly(GetType()).GetName().Name}-{GetType().Name}-{nameof(TestLogin_UserUnknown)}").Result; + + Assert.That( + l_oSessionCookieJM_Dev1, + Is.Not.Null); + + // From COPRI 4.1 cookie is empty, was null before + Assert.IsEmpty(l_oSessionCookieJM_Dev1.authcookie); + + Assert.AreEqual( + "authorization", + l_oSessionCookieJM_Dev1.response); + + Assert.AreEqual( + "Failure: cannot generate authcookie", + l_oSessionCookieJM_Dev1.response_state); + } + + /// Log out functionality is only for test purposes. + [Test] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#elif !NODEVELSERVER + [Category(CATEGORY_USESDEVELSERVER)] +#endif + public void TestLogout( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] + string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + // Prerequisite: Login must be performed before logging out. + var l_oSessionCookie = CopriCallsHttpsReference.DoAuthorizeCall( + url, + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Mail, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Pwd, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.DeviceId); + + Assert.IsFalse( + string.IsNullOrEmpty(l_oSessionCookie?.authcookie), + "Prerequisites not matched: User must be logged on before beeing able to log out."); + + // Verify logout + try + { + Assert.NotNull(DoAuthoutAsync( + url, + new RequestBuilderLoggedIn( + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie).DoAuthout(), + $"{Assembly.GetAssembly(GetType()).GetName().Name}-{GetType().Name}-{nameof(TestLogout)}" + ).Result.GetIsResponseOk()); + } + finally + { + // Log in again to ensure that tests do not change state of database (assumtion: user is always logged in). + CopriCallsHttpsReference.DoAuthorizeCall( + url, + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Mail, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Pwd, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.DeviceId); + } + } + + /// + /// Log out functionality is only for test purposes. + /// + [Test] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#elif !NODEVELSERVER + [Category(CATEGORY_USESDEVELSERVER)] +#endif + public void TestLogout_NotLoggedIn( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] + string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + // Prerequisite: + // Ensure that user is really logged out and get valid session cookie before verifying logout behavior. + var l_oLoginResponse = CopriCallsHttpsReference.DoAuthorizeCall( + url, + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Mail, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Pwd, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.DeviceId); + + Assert.IsFalse( + string.IsNullOrEmpty(l_oLoginResponse?.authcookie), + "Prerequisites not matched: User must be logged out before beeing able to log out."); + + CopriCallsHttpsReference.DoAuthoutCall(url, TinkApp.MerchantId, l_oLoginResponse.authcookie); + + try + { + // Verify logout + Assert.Throws( + () => DoAuthoutAsync( + url, + new RequestBuilderLoggedIn(TinkApp.MerchantId, l_oLoginResponse.authcookie).DoAuthout(), + $"{Assembly.GetAssembly(GetType()).GetName().Name}-{GetType().Name}-{nameof(TestLogout_NotLoggedIn)}").Result.GetIsResponseOk()); + } + finally + { + // Log in again. Otherwise subsequent tests might fail. + l_oLoginResponse = CopriCallsHttpsReference.DoAuthorizeCall( + new CopriServerUriList().ActiveUri.AbsoluteUri, + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Mail, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Pwd, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.DeviceId); + } + } + + /// + /// Log out is performed by app programmatically if authcookie is no more valid. + /// Must work even if authcookie is no more valid, otherwise login would not be avalable. + /// + [Test] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#elif !NODEVELSERVER + [Category(CATEGORY_USESDEVELSERVER)] +#endif + public void TestLogout_AuthcookieUnknown( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] + string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + // Prerequisite: Login must be formormed before logging out. + var l_oSessionCookie = CopriCallsHttpsReference.DoAuthorizeCall( + url, + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Mail, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Pwd, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.DeviceId); + + Assert.IsFalse( + string.IsNullOrEmpty(l_oSessionCookie?.authcookie), + "Prerequisites not matched: User must be logged on before beeing able to log out."); + + // Verify logout that expected excepton is thrown. + try + { + Assert.AreEqual( + "Failure 1001: authcookie not defined", // Up to 2020-12-05 COPRI returned: "Failure 1004: authcookie not defined" + DoAuthoutAsync( + url, + new RequestBuilderLoggedIn( + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerKeksGibtsNet.AuthCookie).DoAuthout(), + $"{Assembly.GetAssembly(GetType()).GetName().Name}-{GetType().Name}-{nameof(TestLogout_AuthcookieUnknown)}" + ).Result.response_state); + } + finally + { + // Log in again to ensure that tests do not change state of database (assumtion: user is always logged in). + CopriCallsHttpsReference.DoAuthorizeCall( + url, + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Mail, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.Pwd, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.DeviceId); + } + } + + /// + /// From COPRI version v4.1 switched from TINK devel CopriServerUriList.TINK_DEVEL and CopriServerUriList.TINK_LIVE to CopriServerUriList.SHAREE_DEVEL. + /// + [Test] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#elif !NODEVELSERVER + [Category(CATEGORY_USESDEVELSERVER)] +#endif + public void TestGetBikesAvailalbleCall_LoggedId( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + // Check prerequisites: At least one bike must be available. + var bikesReference = CopriCallsHttpsReference.GetBikesAvailableCall(url, TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie)?.bikes; + Assert.NotNull(bikesReference); + var bikeReference = bikesReference.FirstOrDefault().Value; + Assert.That( + bikeReference, + Is.Not.Null, + $"Prerequisites are not matched: No bikes available from server {url} returned but at least one bike for verification required."); + + // Verify list of bikes returned from first device + var request = new RequestBuilderLoggedIn( + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie).GetBikesAvailable(); + + var bikes = GetBikesAvailableAsync( + url, + request).Result.bikes; + + Assert.That( + bikes, + Is.Not.Null, + "Response is null."); + + var bike = bikes.FirstOrDefault().Value; + Assert.That( + bike, + Is.Not.Null, + "Response on GetBikesAvailableCall must contain at leas one bike."); + + // Check if entries are valid. + Assert.Greater( + bike.description.Length, + 0, + "Bike despcription must never be empty."); + + Assert.That( + bike.bike, + Is.Not.Null, + "Bike index must never be null"); + + Assert.AreEqual( + "available", + bike.state, + "Bike state must be available"); + } + + /// + /// From COPRI version v4.1 switched from TINK devel CopriServerUriList.TINK_DEVEL and CopriServerUriList.TINK_LIVE to CopriServerUriList.SHAREE_DEVEL. + /// + [Test] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#elif !NODEVELSERVER + [Category(CATEGORY_USESDEVELSERVER)] +#endif + public void TestGetBikesAvailalbleCall_NotLoggedId( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + // Check prerequisites: At least one bike must be available. + var bikeReference = CopriCallsHttpsReference.GetBikesAvailableCall(url, TinkApp.MerchantId)?.bikes?.FirstOrDefault().Value; + Assert.That( + bikeReference, + Is.Not.Null, + $"Prerequisites are not matched: No bikes available from server {url} returned but at least one bike for verification required."); + + // Verify list of bikes returned from first device + var request = new RequestBuilder(TinkApp.MerchantId).GetBikesAvailable(); + + var bikes = GetBikesAvailableAsync( + url, + request, + $"{Assembly.GetAssembly(GetType()).GetName().Name}-{GetType().Name}-{nameof(TestGetBikesAvailalbleCall_NotLoggedId)}").Result.bikes; + + Assert.That( + bikes, + Is.Not.Null, + "Response is null."); + + var bike = bikes.FirstOrDefault().Value; + Assert.That( + bike, + Is.Not.Null, + "Response on GetBikesAvailableCall must contain at leas one bike."); + + // Check if entries are valid. + Assert.Greater( + bike.description.Length, + 0, + "Bike despcription must never be empty."); + + Assert.That( + bike.bike, + Is.Not.Null, + "Bike index must never be null"); + + Assert.AreEqual( + "available", + bike.state, + "Bike state must be available"); + } + + /// + /// Attention: Behaves different if called with a period smaller 15minutes because bike will alreay be reserved. + /// + [Test, Explicit, Ignore("Avoid testrunner running explicit tests.")] + public void TestDoReserveCall( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] + string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + Assert.Less( + CopriCallsHttpsReference.GetBikesOccupiedCall(url, TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie)?.bikes_occupied.Count, + 3, + "Too many bikes requested/ booked."); + + var l_oBikesAvailable = CopriCallsHttpsReference.GetBikesAvailableCall(CopriServerUriList.DevelopUri.AbsoluteUri, TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie)?.bikes; + + Assert.Greater( + l_oBikesAvailable.Count, + 0, + "There must be at least one bike available."); + + // Id of bike to book + string l_oBikeId = l_oBikesAvailable.ToArray()[0].Value.bike; + + // Check prerequisites. + // State of bike for which to cancel booking must be available. + Assert.NotNull( + CopriCallsHttpsReference.GetBikesAvailableCall(url, TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie)?.bikes?.FirstOrDefault(x => x.Value.bike == l_oBikeId).Value, + "Prerequities check failed: Bike with given id must be available;"); + + var l_oBookingResponse = DoReserveAsync( + CopriServerUriList.DevelopUri.AbsoluteUri, + new RequestBuilderLoggedIn( + TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie).DoReserve(l_oBikeId)).Result; + + try + { + // If response_state is "Failure 2001: booking bike 20 . maybe not available" probale test was run already and bike got reserved. + Assert.AreEqual(string.Format("OK: requested bike {0}", l_oBikeId), l_oBookingResponse.response_state); + } + finally + { + // Clean up to ensure that running tests does not modify data base. + CopriCallsHttpsReference.DoCancelReservationCall(url, TinkApp.MerchantId, l_oBikeId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie); + } + } + + /// + /// Attention: Behaves different if called with a period smaller 15minutes because bike will alreay be reserved. + /// + [Test, Explicit, Ignore("Avoid testrunner running explicit tests.")] + public void TestDoCancelReservationCall( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] + string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + + { + Assert.Less( + CopriCallsHttpsReference.GetBikesOccupiedCall(url, TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie)?.bikes_occupied.Count, + 3, + "Too many bikes requested/ booked."); + + var l_oBikesAvailable = CopriCallsHttpsReference.GetBikesAvailableCall(CopriServerUriList.DevelopUri.AbsoluteUri, TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie)?.bikes; + + Assert.Greater( + l_oBikesAvailable.Count, + 0, + "There must be at least one bike available."); + + // Id of bike to book + string l_oBikeId = l_oBikesAvailable.ToArray()[0].Value.bike; + + // Check prerequisites. + var l_oBikeToCancel = CopriCallsHttpsReference.GetBikesAvailableCall(url, TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie)?.bikes?.FirstOrDefault(x => x.Value.bike == l_oBikeId).Value; + if (l_oBikeToCancel != null) + { + // Bike is avilable. Do request before unning test. + CopriCallsHttpsReference.DoReserveCall(url, TinkApp.MerchantId, l_oBikeId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie); + } + + // State of bike for which to cancel booking must be reserved. + var l_oReservedBike = CopriCallsHttpsReference.GetBikesOccupiedCall(url, TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie).bikes_occupied.FirstOrDefault(x => x.Value.bike == l_oBikeId).Value; + Assert.NotNull(l_oReservedBike, string.Format("Setup test failed. Bike with id {0} must be booked before verifying cancel of booking.", l_oReservedBike)); + Assert.AreEqual("requested", l_oReservedBike.state, string.Format("Setup test failed. Bike with id {0} must be booked before verifying cancel of booking.", l_oReservedBike)); + + // Test cancel booking + var l_oBookingResponse = DoCancelReservationAsync( + CopriServerUriList.DevelopUri.AbsoluteUri, + new RequestBuilderLoggedIn( + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie).DoReserve(l_oBikeId)).Result; + + try + { + Assert.AreEqual(string.Format("OK: canceled bike {0}", l_oBikeId), l_oBookingResponse.response_state); + } + catch + { + // Clean up to ensure that running tests does not modify data base. + CopriCallsHttpsReference.DoCancelReservationCall(url, TinkApp.MerchantId, l_oBikeId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie); + } + } + + /// Tests the member. + /// Timecode is no more verified since COPRI 4.1. + [Test] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#elif !NODEVELSERVER + [Category(CATEGORY_USESDEVELSERVER)] +#endif + public void TestGetBikesOccupiedCall_SomeRequestedBooked( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] + string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + BikesReservedOccupiedResponse l_oBookingResponse; + var bikesOccupied = CopriCallsHttpsReference.GetBikesOccupiedCall(url, TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie)?.bikes_occupied; + if (bikesOccupied == null || bikesOccupied.Count < 1) + { + // There must be at least one bike booked. + var bikesAvailable = CopriCallsHttpsReference.GetBikesAvailableCall( + url, + TinkApp.MerchantId, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie)?.bikes; + + Assert.Greater( + bikesAvailable.Count, + 0, + "Prerequisites are not matched: There must be at least one bike available."); + + // Id of bike to book + var bike = bikesAvailable.ToArray()[0].Value; + + l_oBookingResponse = CopriCallsHttpsReference.DoReserveCall( + bike.uri_operator + "/APIjsonserver", + TinkApp.MerchantId, + bike.bike, + TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie); + + Assert.That(l_oBookingResponse.GetIsResponseOk("Testing cotext"), + !Is.Null, + $"Booking must succeed. {l_oBookingResponse.response_text}"); + } + + // Verify GetBikesOccupied call. + var l_oBike = CopriCallsHttpsReference.GetBikesOccupiedCall(url, TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie)?.bikes_occupied?.FirstOrDefault().Value; + Assert.NotNull(l_oBike, "Response on GetBikesOccupiedCall of must contain at least one bike."); + + l_oBookingResponse = GetBikesOccupiedAsync( + url, + new RequestBuilderLoggedIn(TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie).GetBikesOccupied(), + $"{Assembly.GetAssembly(GetType()).GetName().Name}-{GetType().Name}-{nameof(TestGetBikesOccupiedCall_SomeRequestedBooked)}").Result; + + // Check first entry. + Assert.AreEqual( + string.Format("{0}{1}", TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie, TinkApp.MerchantId), + l_oBookingResponse.authcookie); + + Assert.Greater(l_oBike.description.Length, 0, "Bike despcription must never be empty."); + Assert.That(l_oBike.bike, Is.Not.Null, "Bike index must not be null."); + Assert.That( + l_oBike.station, + Is.Not.Null, + "Station index must never be null"); + Assert.Greater(l_oBike.state.Length, 0, "State info must never be null or empty."); + // Todo: Requested bikes do not have a gps position. What is about booked bikes? + // Assert.Greater(l_oBike.gps.Length, 0, "Gps position must never be empty."); + Assert.Greater(l_oBike.start_time.Length, 0, "Time when request/ booking was performed must never be null or empty."); + } + + /// + /// Tests the member. + /// Call GetBikesOccupiedCall is first call of app which fails (throws AuthcookieNotDefinedException) if auth cookie is invalid. + /// If app detects AuthcookieNotDefinedException exception user is logged out. + /// + [Test] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#elif !NODEVELSERVER + [Category(CATEGORY_USESDEVELSERVER)] +#endif + public void TestGetBikesOccupiedCall_KeksGibtsNet( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + var request = new RequestBuilderLoggedIn(TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerKeksGibtsNet.AuthCookie).GetBikesOccupied(); + + var l_oBookingResponse = GetBikesOccupiedAsync( + url, + request, + $"{Assembly.GetAssembly(GetType()).GetName().Name}-{GetType().Name}-{nameof(TestGetBikesOccupiedCall_KeksGibtsNet)}").Result; + + Assert.AreEqual( + "Failure 1001: authcookie on primary not defined", + l_oBookingResponse.response_state); // Up to 2020-12-05 COPRI returned: "Failure 1003: authcookie not defined" + } + + /// + /// Tests the member. + /// + /// + /// From COPRI version v4.1 switched from TINK devel CopriServerUriList.TINK_DEVEL and CopriServerUriList.TINK_LIVE to CopriServerUriList.SHAREE_DEVEL. + /// + [Test] + [Category(CATEGORY_REQUIRESCOPRI)] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#endif + public void TestGetStationsAllCall_NotLoggedIn( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + // Check prerequisites: At least one bike must be available. + var l_oStationsReference = CopriCallsHttpsReference.GetStationsAllCall(url, TinkApp.MerchantId)?.stations; + Assert.IsTrue( + l_oStationsReference != null && l_oStationsReference.Count > 0, + "Prerequisites are not matched: There are no stations."); + + // Verify implementation + var l_oStationsAll = GetStationsAsync(url, new RequestBuilder(TinkApp.MerchantId).GetStations()).Result; + Assert.NotNull(l_oStationsAll?.stations); + Assert.Greater(l_oStationsAll.stations.Count, 0); + } + + /// + /// Tests the member. + /// + /// + /// From COPRI version v4.1 switched from TINK devel CopriServerUriList.TINK_DEVEL and CopriServerUriList.TINK_LIVE to CopriServerUriList.SHAREE_DEVEL. + /// + [Test] +#if !NOLIVESERVER + [Category(CATEGORY_USESLIVESERVER)] +#elif !NODEVELSERVER + [Category(CATEGORY_USESDEVELSERVER)] +#endif + public void TestGetStationsAllCall_LoggedIn( +#if NOLIVESERVER + [Values(CopriServerUriList.SHAREE_DEVEL)] string url) +#elif NODEVELSERVER + [Values(CopriServerUriList.SHAREE_LIVE)] string url) +#else + [Values(CopriServerUriList.SHAREE_DEVEL, CopriServerUriList.SHAREE_LIVE)] string url) +#endif + { + var stationsAll = GetStationsAsync( + url, + new RequestBuilderLoggedIn(TinkApp.MerchantId, TestTINKLib.LoginSessionCopriInfo.JavaministerHardwareNr1.AuthCookie).GetStations(), + $"{Assembly.GetAssembly(GetType()).GetName().Name}-{GetType().Name}-{nameof(TestGetStationsAllCall_LoggedIn)}").Result; + + Assert.NotNull(stationsAll?.stations); + Assert.That( + stationsAll.stations.Count, + Is.GreaterThan(0), + $"There must be at least one station."); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriCallsMemory.cs.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriCallsMemory.cs.cs new file mode 100644 index 0000000..8649ce1 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriCallsMemory.cs.cs @@ -0,0 +1,74 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using TINK.Repository; +using TINK.Repository.Response; +using static TINK.Repository.CopriCallsMemory; + +namespace TestTINKLib.Fixtures.Connector.Request +{ + + [TestFixture] + public class TestCopriCallsMemory + { + [Test] + public void TestConsistency() + { + foreach (SampleSets l_oSampleSet in Enum.GetValues(typeof(SampleSets))) + { + var l_oCopri = new CopriCallsMemory(l_oSampleSet, 1, "4da3044c8657a04ba60e2eaa753bc51a"); + + for (var l_iStageIndex = 1; l_iStageIndex <= l_oCopri.StagesCount; l_iStageIndex++) + { + Assert.That(l_oCopri.GetBikesAvailableAsync().Result?.bikes, Is.Not.Null, $"There must be at least one bike for sample set {l_oSampleSet}, stage {l_iStageIndex}."); + VerifyBikeIdIsUnique(l_oCopri); + + Assert.IsNull( + l_oCopri.GetBikesAvailableAsync().Result.bikes.Values.FirstOrDefault(x => x.state != "available"), + "Bikes available must return bikes which are all of state available."); + + Assert.IsNull( + l_oCopri.GetBikesOccupiedAsync().Result.bikes_occupied.Values.FirstOrDefault(x => x.state == "available"), + "Bikes occupied must return bikes which are either reserved or booked."); + } + } + } + + /// + /// Test consistency for a single sample set, + /// + /// + private void VerifyBikeIdIsUnique(CopriCallsMemory p_oMemory) + { + Dictionary l_oChecker = new Dictionary(); + + var l_oBikesAvailable = p_oMemory.GetBikesAvailableAsync().Result; + foreach (var l_oBike in l_oBikesAvailable.bikes.Values) + { + Assert.IsFalse( + l_oChecker.Keys.Contains(l_oBike.bike), + string.Format( + "Bike form available bikes with id {0} already exist in dictionary. Sample set is {1}, stage index {2}.", + l_oBike.bike, + p_oMemory.ActiveSampleSet, + p_oMemory.ActiveStageIndex)); + + l_oChecker.Add(l_oBike.bike, l_oBike); + } + + var l_oBikesOccupied = p_oMemory.GetBikesOccupiedAsync().Result; + foreach (var l_oBike in l_oBikesOccupied.bikes_occupied.Values) + { + Assert.IsFalse( + l_oChecker.Keys.Contains(l_oBike.bike), + string.Format( + "Bike from occupied bikes with id {0} already exist in dictionary. Sample set is {1}, stage index {2}.", + l_oBike.bike, + p_oMemory.ActiveSampleSet, + p_oMemory.ActiveStageIndex)); + l_oChecker.Add(l_oBike.bike, l_oBike); + } + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriCallsStatic.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriCallsStatic.cs new file mode 100644 index 0000000..94a5daa --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriCallsStatic.cs @@ -0,0 +1,163 @@ +using System.Linq; +using NUnit.Framework; +using TINK.Repository; +using TINK.Repository.Response; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector.Request +{ + [TestFixture] + public class TestCopriCallsStatic + { + [Test] + public void TestDeserializeObjectBikesAvailableValidResponse() + { + const string VALID_RESPONSE = @" + { + ""shareejson"": { + + ""authcookie"": 0, + ""apiserver"": ""https://tinkwwp.copri-bike.de"", + ""response"": ""bikes_available"", + ""bikes"": { + ""3399"": { + ""description"": ""Cargo Trike"", + ""bike"": ""26"", + ""state"": ""available"", + ""gps"" : { ""latitude"": ""47.6586936667"", ""longitude"": ""9.16863116667"" }, + ""station"" : ""4"" + + }, + }, + ""response_state"": ""OK"", + ""copri_version"" : ""4.1.0.0"" + } + } + "; + + // Ensure that answer holds a valid bike. + var l_oBike = CopriCallsStatic.DeserializeResponse(VALID_RESPONSE).bikes.FirstOrDefault().Value; + Assert.NotNull(l_oBike, "Response must contain at leas one bike."); + Assert.Greater(l_oBike.description.Length, 0, "Bike despcription must never be empty."); + Assert.AreEqual(l_oBike.bike, "26"); + Assert.That( + l_oBike.station, + Is.EqualTo("4"), + "Station index must never be negative"); + Assert.AreEqual("available", l_oBike.state); + Assert.That(l_oBike.gps, Is.Not.Null, "Gps position must never be empty."); + } + + [Test] + public void TestDeserializeObjectBikesAvailableValidResponse_NoDescription() + { + const string INVALID_RESPONSE = @" + { + ""shareejson"": { + + ""authcookie"": 0, + ""apiserver"": ""https://tinkwwp.copri-bike.de"", + ""response"": ""bikes_available"", + ""bikes"": { + ""3399"": { + ""bike"": 26, + ""state"": ""available"", + ""gps"" : { ""latitude"": ""47.6586936667"", ""longitude"": ""9.16863116667"" }, + ""station"" : 4 + + }, + }, + ""response_state"": ""OK"", + ""copri_version"" : ""4.1.0.0"", + } + } + "; + + // Ensure that answer holds a valid bike. + var l_oBike = CopriCallsStatic.DeserializeResponse(INVALID_RESPONSE).bikes.FirstOrDefault().Value; + Assert.NotNull(l_oBike, "Response must contain at leas one bike."); + Assert.IsNull(l_oBike.description); + Assert.That(l_oBike.bike, Is.Not.Null); + Assert.That(l_oBike.station, Is.Not.Null); + Assert.AreEqual("available", l_oBike.state); + Assert.That(l_oBike.gps, Is.Not.Null, "Gps position must never be empty."); + } + + [Test] + public void TestDeserializeObjectBikesAvailableValidResponse_NoBikeId() + { + const string VALID_RESPONSE = @" + { + ""shareejson"": { + + ""authcookie"": 0, + ""apiserver"": ""https://tinkwwp.copri-bike.de"", + ""response"": ""bikes_available"", + ""bikes"": { + ""3399"": { + ""description"": ""Cargo Trike"", + ""state"": ""available"", + ""gps"" : { ""latitude"": ""47.6586936667"", ""longitude"": ""9.16863116667"" }, + ""station"" : ""4"" + }, + }, + ""response_state"": ""OK"", + ""copri_version"" : ""4.1.0.0"", + } + }"; + + // Ensure that answer holds a valid bike. + var l_oBike = CopriCallsStatic.DeserializeResponse(VALID_RESPONSE).bikes.FirstOrDefault().Value; + Assert.NotNull(l_oBike, "Response must contain at leas one bike."); + Assert.Greater(l_oBike.description.Length, 0, "Bike despcription must never be empty."); + Assert.That(l_oBike.bike, Is.Null); + Assert.That(l_oBike.station, Is.Not.Null); + Assert.AreEqual("available", l_oBike.state); + Assert.That(l_oBike.gps, Is.Not.Null, "Gps position must never be empty."); + } + + [Test] + public void TestDeserializeObjectBikesOccupiedValidResponse() + { + const string VALID_RESPONSE = @" + { + ""shareejson"": { + ""response_state"": ""OK"", + ""bikes_occupied"": { + ""87781"": { + ""timeCode"": ""3630"", + ""state"": ""occupied"", + ""station"" : ""5"", + ""description"": ""Cargo Long"", + ""start_time"": ""2017-11-28 11:01:51.637747+01"", + ""bike"": ""8"" + }, + ""87782"": { + ""timeCode"": ""2931"", + ""state"": ""occupied"", + ""station"" : ""4"", + ""description"": ""Cargo Long"", + ""start_time"": ""2017-11-28 13:06:55.147368+01"", + ""bike"": ""7"" + } + }, + ""authcookie"": ""b76b97e43a2d76b8499f32e6dd597af8"", + ""response"": ""user_bikes_occupied"", + ""apiserver"": ""https://tinkwwp.copri-bike.de"", + ""copri_version"" : ""4.1.0.0"", + } + }"; + + // Ensure that answer holds a valid bike. + var l_oBike = CopriCallsStatic.DeserializeResponse(VALID_RESPONSE).bikes_occupied.FirstOrDefault().Value; + Assert.NotNull(l_oBike, "Response must contain at leas one bike."); + Assert.Greater(l_oBike.description.Length, 0, "Bike despcription must never be empty."); + Assert.That(l_oBike.bike, Is.Not.Null); + Assert.That(l_oBike.station, Is.Not.Null); + Assert.Greater(l_oBike.state.Length, 0, "State info must never be null or empty."); + // Todo: Requested bikes do not have a gps position. What is about booked bikes? + // Assert.Greater(l_oBike.gps.Length, 0, "Gps position must never be empty."); + Assert.Greater(l_oBike.start_time.Length, 0, "Time when request/ booking was performed must never be null or empty."); + Assert.Greater(l_oBike.timeCode.Length, 0, "Booking code must never be null or empty."); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriServerUriList.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriServerUriList.cs new file mode 100644 index 0000000..a06190c --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/TestCopriServerUriList.cs @@ -0,0 +1,69 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TINK.Model.Connector; +using TINK.Model.Services.CopriApi.ServerUris; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector +{ + [TestFixture] + public class TestCopriServerUriList + { + [Test] + public void TestConstruct() + { + var l_oUri = new CopriServerUriList(); + + Assert.Greater(l_oUri.Uris.Count, 0, "There must be at least one uri"); + Assert.NotNull(l_oUri.ActiveUri); + } + + [Test] + public void TestConstruct_AryStringString() + { + var l_oUri = new CopriServerUriList( + (new List { new Uri("http://1.2.3.4"), new Uri("http://2.3.4.5"), new Uri("http://3.4.5.6") }).ToArray(), + new Uri("http://2.3.4.5")); + + Assert.AreEqual(3, l_oUri.Uris.Count); + Assert.AreEqual(new Uri("http://2.3.4.5"), l_oUri.ActiveUri); + } + + [Test] + public void TestConstruct_AryStringString_NullList() + { + Assert.Throws(() => new CopriServerUriList( + null, + new Uri("http://2.3.4.5"))); + } + + [Test] + public void TestConstruct_AryStringString_InvalidList() + { + Assert.Throws( () => new CopriServerUriList( + (new List()).ToArray(), + new Uri("http://2.3.4.5"))); + } + + [Test] + public void TestConstruct_AryStringString_InvalidActiveUri() + { + Assert.Throws(() => new CopriServerUriList( + (new List { new Uri("http://1.2.3.4"), new Uri("http://2.3.4.5"), new Uri("http://3.4.5.6") }).ToArray(), + new Uri("http://9.9.9.9"))); + } + + + [Test] + public void TestDefaultActiveUri() + { + Assert.AreEqual( + "https://shareeapp-primary.copri.eu/APIjsonserver", + CopriServerUriList.DefaultActiveUri.AbsoluteUri, + "In production environment, server address must always be app.tink-konstanz.de/APIjsonserver."); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/TestFilter.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/TestFilter.cs new file mode 100644 index 0000000..e0d446b --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/TestFilter.cs @@ -0,0 +1,94 @@ +using NUnit.Framework; +using Rhino.Mocks; +using System.Collections.Generic; +using TINK.Model.Connector; +using System.Linq; +using TINK.Repository; + +namespace TestTINKLib.Fixtures.ObjectTests.Connector +{ + /// Tests filter object. + [TestFixture] + public class TestFilter + { + /// Tests all stations. + [Test] + public void TestGetStationsAll() + { + var l_oConnector = new ConnectorCache( + string.Empty, + string.Empty, + new CopriCallsMemory(CopriCallsMemory.SampleSets.Set2, 1)); + + var l_oFilter = new FilteredConnector(new List { "TINK", "Konrad" }, l_oConnector); + var l_oStations = l_oFilter.Query.GetBikesAndStationsAsync().Result.Response; + Assert.AreEqual(9, l_oStations.StationsAll.Count()); + + l_oFilter = new FilteredConnector(new List { "TINK" }, l_oConnector); + l_oStations = l_oFilter.Query.GetBikesAndStationsAsync().Result.Response; + Assert.AreEqual(8, l_oStations.StationsAll.Count()); + + l_oFilter = new FilteredConnector(new List { "Konrad" }, l_oConnector); + l_oStations = l_oFilter.Query.GetBikesAndStationsAsync().Result.Response; + Assert.AreEqual(2, l_oStations.StationsAll.Count()); + + l_oFilter = new FilteredConnector(new List { "AGroupNamedNonsensDoesNotExist" }, l_oConnector); + l_oStations = l_oFilter.Query.GetBikesAndStationsAsync().Result.Response; + Assert.AreEqual(0, l_oStations.StationsAll.Count()); + + l_oFilter = new FilteredConnector(new List(), l_oConnector); + l_oStations = l_oFilter.Query.GetBikesAndStationsAsync().Result.Response; + Assert.AreEqual(9, l_oStations.StationsAll.Count()); + + l_oFilter = new FilteredConnector(null, l_oConnector); + l_oStations = l_oFilter.Query.GetBikesAndStationsAsync().Result.Response; + Assert.AreEqual(9, l_oStations.StationsAll.Count(), "Null means filter none."); + } + + /// Tests all stations. + [Test] + public void TestGetBikesAll() + { + var l_oConnector = new ConnectorCache( + string.Empty, + string.Empty, + new CopriCallsMemory(CopriCallsMemory.SampleSets.Set2, 1)); + + var l_oFilter = new FilteredConnector(new List { "TINK", "Konrad" }, l_oConnector); + var l_oBikes = l_oFilter.Query.GetBikesAsync().Result.Response; + Assert.AreEqual(12, l_oBikes.Count()); + + l_oFilter = new FilteredConnector(new List { "TINK" }, l_oConnector); + l_oBikes = l_oFilter.Query.GetBikesAsync().Result.Response; + Assert.AreEqual(11, l_oBikes.Count()); + + l_oFilter = new FilteredConnector(new List { "Konrad" }, l_oConnector); + l_oBikes = l_oFilter.Query.GetBikesAsync().Result.Response; + Assert.AreEqual(1, l_oBikes.Count()); + + l_oFilter = new FilteredConnector(new List { "AGroupNamedNonsensDoesNotExist" }, l_oConnector); + l_oBikes = l_oFilter.Query.GetBikesAsync().Result.Response; + Assert.AreEqual(0, l_oBikes.Count()); + + l_oFilter = new FilteredConnector(new List(), l_oConnector); + l_oBikes = l_oFilter.Query.GetBikesAsync().Result.Response; + Assert.AreEqual(12, l_oBikes.Count(), "List with zero element means filter all."); + + l_oFilter = new FilteredConnector(null, l_oConnector); + l_oBikes = l_oFilter.Query.GetBikesAsync().Result.Response; + Assert.AreEqual(12, l_oBikes.Count(), "Null means filter none."); + } + + [Test] + public void TestIsConnected() + { + var l_oMock = MockRepository.GenerateMock(); + l_oMock.Expect(x => x.IsConnected).Return(true) ; + Assert.IsTrue(new FilteredConnector(new List(), l_oMock).IsConnected); + + l_oMock = MockRepository.GenerateMock(); + l_oMock.Expect(x => x.IsConnected).Return(false); + Assert.IsFalse(new FilteredConnector(new List(), l_oMock).IsConnected); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Connector/TestTextToTypeHelper.cs b/TestTINKLib/Fixtures/ObjectTests/Connector/TestTextToTypeHelper.cs new file mode 100644 index 0000000..a0cc667 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Connector/TestTextToTypeHelper.cs @@ -0,0 +1,615 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using System.Linq; +using TINK.Model.Bike; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.Repository.Response; +using JsonConvertRethrow = TINK.Repository.Response.JsonConvertRethrow; + +namespace TestTINKLib.Fixtures.Connector +{ + + [TestFixture] + public class TestTextToTypeHelper + { + [Test] + public void TestGetWheelType_InvalidDescription() + { + var l_oInfo = new BikeInfoBase(); + + // Verify prerequisites + Assert.IsNull(l_oInfo.description); + + // Verify behaviour of member. + Assert.IsNull(TextToTypeHelper.GetWheelType(l_oInfo)); + } + + [Test] + public void TestGetWheelType() + { + var l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""2"", + ""bike_group"" : [ ""TINK"" ], + ""description"" : ""Cargo Long"", + ""gps"" : { ""latitude"": ""47.6612083333"", ""longitude"": ""9.16637533333"" }, + ""station"" : ""9"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.AreEqual(WheelType.Two, TextToTypeHelper.GetWheelType(l_oInfo)); + + l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""11"", + ""bike_group"" : [ ""TINK"" ], + ""description"" : ""Cargo Trike"", + ""gps"" : { ""latitude"": ""47.665051"", ""longitude"": ""9.174096"" }, + ""station"" : ""1"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.AreEqual(WheelType.Trike, TextToTypeHelper.GetWheelType(l_oInfo)); + + l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""51"", + ""bike_group"" : [ ""Konrad"" ], + ""description"" : ""Demo Stadtrad"", + ""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" }, + ""station"" : ""8"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.AreEqual(WheelType.Two, TextToTypeHelper.GetWheelType(l_oInfo)); + + l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""51"", + ""bike_group"" : [ ""Konrad"" ], + ""description"" : ""Stadtrad"", + ""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" }, + ""station"" : ""8"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.AreEqual(WheelType.Two, TextToTypeHelper.GetWheelType(l_oInfo)); + } + + [Test] + public void TestGetTypeOfBike_InvalidDescription() + { + var l_oInfo = new BikeInfoBase(); + + // Verify prerequisites + Assert.IsNull(l_oInfo.description); + + // Verify behaviour of member. + Assert.IsNull(TextToTypeHelper.GetTypeOfBike(l_oInfo)); + } + + [Test] + public void TestGetTypeOfBike() + { + var l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""2"", + ""bike_group"" : [ ""TINK"" ], + ""description"" : ""Cargo Long"", + ""gps"" : { ""latitude"": ""47.6612083333"", ""longitude"": ""9.16637533333"" }, + ""station"" : ""9"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.AreEqual(TypeOfBike.Cargo, TextToTypeHelper.GetTypeOfBike(l_oInfo)); + + l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""11"", + ""bike_group"" : [ ""TINK"" ], + ""description"" : ""Cargo Trike"", + ""gps"" : { ""latitude"": ""47.665051"", ""longitude"": ""9.174096"" }, + ""station"" : ""1"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.AreEqual(TypeOfBike.Cargo, TextToTypeHelper.GetTypeOfBike(l_oInfo)); + + l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""51"", + ""bike_group"" : [ ""Konrad"" ], + ""description"" : ""Demo Stadtrad"", + ""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" }, + ""station"" : ""8"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.AreEqual(TypeOfBike.Citybike, TextToTypeHelper.GetTypeOfBike(l_oInfo)); + + l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""51"", + ""bike_group"" : [ ""Konrad"" ], + ""description"" : ""Stadtrad"", + ""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" }, + ""station"" : ""8"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.AreEqual(TypeOfBike.Citybike, TextToTypeHelper.GetTypeOfBike(l_oInfo)); + } + + [Test] + public void TestGetState_InvalidDescription() + { + var l_oInfo = new BikeInfoBase(); + + // Verify prerequisites + Assert.IsNull(l_oInfo.state); + + // Verify behaviour of member. + Assert.Throws>(() => TextToTypeHelper.GetState(l_oInfo)); + } + + [Test] + public void TestGetIsDemo() + { + var l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""2"", + ""bike_group"" : [ ""TINK"" ], + ""description"" : ""Cargo Long"", + ""gps"" : { ""latitude"": ""47.6612083333"", ""longitude"": ""9.16637533333"" }, + ""station"" : ""9"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.IsFalse(TextToTypeHelper.GetIsDemo(l_oInfo)); + + l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""11"", + ""bike_group"" : [ ""TINK"" ], + ""description"" : ""Cargo Trike"", + ""gps"" : { ""latitude"": ""47.665051"", ""longitude"": ""9.174096"" }, + ""station"" : ""1"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.IsFalse(TextToTypeHelper.GetIsDemo(l_oInfo)); + + l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""51"", + ""bike_group"" : [ ""Konrad"" ], + ""description"" : ""Demo Stadtrad"", + ""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" }, + ""station"" : ""8"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.IsTrue(TextToTypeHelper.GetIsDemo(l_oInfo)); + + l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""bike"" : ""51"", + ""bike_group"" : [ ""Konrad"" ], + ""description"" : ""Stadtrad"", + ""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" }, + ""station"" : ""8"", + ""state"" : ""available"" + }"); + + // Verify behaviour of member. + Assert.IsFalse(TextToTypeHelper.GetIsDemo(l_oInfo)); + } + + [Test] + public void TestGetPosition() + { + Assert.AreEqual(1.234d, TextToTypeHelper.GetPosition(JsonConvert.DeserializeObject("{ \"latitude\" : \"1.234\", \"longitude\" : \"5.678\"}")).Latitude); + Assert.AreEqual(5.678d, TextToTypeHelper.GetPosition(JsonConvert.DeserializeObject("{ \"latitude\" : \"1.234\", \"longitude\" : \"5.678\"}")).Longitude); + } + + [Test] + public void TestGetStationGroup_Invalid() + { + var l_oStation = JsonConvertRethrow.DeserializeObject( + @"{ + ""station"" : ""4"", + ""gps"" : { ""latitude"": ""47.6586936667"", ""longitude"": ""9.16863116667"" } + }"); + + // From COPRI version v4.1 no more exception thrown. + Assert.That(l_oStation.GetGroup().Count(), Is.EqualTo(0)); + } + + [Test] + public void TestGetStationGroup_TINK() + { + var l_oStation = JsonConvertRethrow.DeserializeObject( + @"{ + ""station"" : ""4"", + ""station_group"" : [ ""TINK"" ], + ""gps"" : { ""latitude"": ""47.6586936667"", ""longitude"": ""9.16863116667"" } + }"); + + Assert.AreEqual("TINK", string.Join(",", l_oStation.GetGroup().ToArray())); + } + + [Test] + public void TestGetStationGroup_TINKAndKonrad() + { + var l_oStation = JsonConvertRethrow.DeserializeObject( + @"{ + ""station"" : ""4"", + ""station_group"": [ ""TINK"", ""Konrad"" ], + ""gps"" : { ""latitude"": ""47.6586936667"", ""longitude"": ""9.16863116667"" } + }"); + + Assert.AreEqual("TINK,Konrad", string.Join(",", l_oStation.GetGroup().ToArray())); + } + + [Test] + public void TestGetBikeGroup_TINK() + { + var l_oBike = JsonConvertRethrow.DeserializeObject( + @"{ + ""state"" : ""available"", + ""bike"" : ""18"", + ""description"" : ""Cargo Long"", + ""bike_group"" : [ ""TINK"" ], + ""station"" : ""13"", + }"); + + Assert.AreEqual("TINK", string.Join(",", l_oBike.GetGroup().ToArray())); + } + + [Test] + public void TestGetBikeGroup_TINKCopri() + { + var l_oBike = JsonConvertRethrow.DeserializeObject( + @"{ + ""state"" : ""available"", + ""bike"" : ""18"", + ""description"" : ""Cargo Long"", + ""bike_group"" : [ ""TINK"" ], + ""system"" : ""BC"", + ""station"" : ""13"", + }"); + + Assert.AreEqual("TINK", string.Join(",", l_oBike.GetGroup().ToArray())); + } + + [Test] + public void TestGetBikeGroup_TINKSMS() + { + var l_oBike = JsonConvertRethrow.DeserializeObject( + @"{ + ""state"" : ""available"", + ""bike"" : ""18"", + ""description"" : ""Cargo Long"", + ""bike_group"" : [ ""TINK"" ], + ""system"" : ""Lock"", + ""station"" : ""13"", + }"); + + Assert.AreEqual("TINK", string.Join(",", l_oBike.GetGroup().ToArray())); + } + + [Test] + public void TestGetBikeGroup_Konrad() + { + var l_oBike = JsonConvertRethrow.DeserializeObject( + @"{ + ""state"" : ""available"", + ""bike"" : ""18"", + ""description"" : ""Cargo Long"", + ""bike_group"" : [ ""Konrad"" ], + ""station"" : ""13"", + }"); + + Assert.AreEqual("Konrad", string.Join(",", l_oBike.GetGroup().ToArray())); + } + + [Test] + public void TestGetBikeGroup_Null() + { + var l_oBike = JsonConvertRethrow.DeserializeObject( + @"{ + ""state"" : ""available"", + ""bike"" : ""18"", + ""description"" : ""Cargo Long"", + ""station"" : ""13"", + }"); + + Assert.AreEqual(0, l_oBike.GetGroup().ToArray().Length); + } + [Test] + public void TestGetAuthGroup() + { + var l_oResponse = JsonConvertRethrow.DeserializeObject(@" + { + ""response"" : ""authorization"", + ""authcookie"" : ""4da3044c8657a04ba60e2eaa753bc51a"", + ""user_group"" : [ ""TINK"", ""Konrad"" ], + ""response_state"" : ""OK"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"" + }"); + + Assert.AreEqual(2, l_oResponse.GetGroup().ToList().Count); + Assert.AreEqual(FilterHelper.FILTERTINKGENERAL, l_oResponse.GetGroup().ToList()[0]); + Assert.AreEqual("Konrad", l_oResponse.GetGroup().ToList()[1]); + } + + [Test] + public void TestGetGroupString() + { + // From COPRI version v4.1 no more exception thrown. + Assert.That(TextToTypeHelper.GetGroup(new string[0]).Count(), Is.EqualTo(0)); + } + + [Test] + public void TestGetGroupString_Null() + { + // From COPRI version v4.1 no more exception thrown. + Assert.That(TextToTypeHelper.GetGroup((string[])null).Count(), Is.EqualTo(0)); + } + + [Test] + public void TestGetGroupString_Roundtrip() + { + Assert.AreEqual("Tunk,Unk", TextToTypeHelper.GetGroup(TextToTypeHelper.GetGroup(new [] { "Tunk", "Unk" }))); + } + + [Test] + public void TestGetUserKey() + { + var l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""total_price"": ""0.00"", + ""gps"" : { ""latitude"": ""47.6586133"", ""longitude"": ""9.16864"" }, + ""unit_price"": ""3.00"", + ""K_u"": ""[99, 104, 120, 121, 63, 99, -10, -110, 94, 70, 15, -112, -6, 101, 117, -90, -113, -54, -90, -95, 0, 0, 0, 0]"", + ""tariff_description"": {""name"" : ""TINK Basic""}, + ""end_time"": ""2020-04-07 16:55:18"", + ""K_seed"": ""[-18, -80, 20, -90, 3, 69, 96, 4, -35, 75, -95, 102, 7, 121, -122, 15]"", + ""system"": ""Ilockit"", + ""bike"": ""16"", + ""computed_hours"": ""0"", + ""request_time"": ""2020-04-07 16:55:06.823436+02"", + ""bike_group"" : [ ""TINK"" ], + ""K_a"": ""[-19, 29, -60, 29, 35, -121, -69, 93, 27, -122, 107, -127, -30, 74, 82, 12, 4, -20, 40, 16, 0, 0, 0, 0]"", + ""state"": ""occupied"", + ""real_hours"": ""0"", + ""station"" : ""7"", + ""start_time"": ""2020-04-07 16:55:17.786551+02"", + ""description"": ""Cargo Long"" + }"); + + Assert.AreEqual( + 99, + TextToTypeHelper.GetUserKey(l_oInfo)[0]); + Assert.AreEqual( + 104, + TextToTypeHelper.GetUserKey(l_oInfo)[1]); + } + + [Test] + public void TestGetAdminKey() + { + var l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""total_price"": ""0.00"", + ""gps"" : { ""latitude"": ""47.6586133"", ""longitude"": ""9.16864"" }, + ""unit_price"": ""3.00"", + ""K_u"": ""[99, 104, 120, 121, 63, 99, -10, -110, 94, 70, 15, -112, -6, 101, 117, -90, -113, -54, -90, -95, 0, 0, 0, 0]"", + ""tariff_description"": {""name"" : ""TINK Basic""}, + ""end_time"": ""2020-04-07 16:55:18"", + ""K_seed"": ""[-18, -80, 20, -90, 3, 69, 96, 4, -35, 75, -95, 102, 7, 121, -122, 15]"", + ""system"": ""Ilockit"", + ""bike"": ""16"", + ""computed_hours"": ""0"", + ""request_time"": ""2020-04-07 16:55:06.823436+02"", + ""bike_group"" : [ ""TINK"" ], + ""K_a"": ""[-19, 29, -60, 29, 35, -121, -69, 93, 27, -122, 107, -127, -30, 74, 82, 12, 4, -20, 40, 16, 0, 0, 0, 0]"", + ""state"": ""occupied"", + ""real_hours"": ""0"", + ""station"" : ""7"", + ""start_time"": ""2020-04-07 16:55:17.786551+02"", + ""description"": ""Cargo Long"" + }"); + + Assert.AreEqual( + 237, + TextToTypeHelper.GetAdminKey(l_oInfo)[0]); + Assert.AreEqual( + 29, + TextToTypeHelper.GetAdminKey(l_oInfo)[1]); + } + + [Test] + public void TestGetSeed() + { + var l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""total_price"": ""0.00"", + ""gps"" : { ""latitude"": ""47.6586133"", ""longitude"": ""9.16864"" }, + ""unit_price"": ""3.00"", + ""K_u"": ""[99, 104, 120, 121, 63, 99, -10, -110, 94, 70, 15, -112, -6, 101, 117, -90, -113, -54, -90, -95, 0, 0, 0, 0]"", + ""tariff_description"": {""name"" : ""TINK Basic""}, + ""end_time"": ""2020-04-07 16:55:18"", + ""K_seed"": ""[-18, -80, 20, -90, 3, 69, 96, 4, -35, 75, -95, 102, 7, 121, -122, 15]"", + ""system"": ""Ilockit"", + ""bike"": ""16"", + ""computed_hours"": ""0"", + ""request_time"": ""2020-04-07 16:55:06.823436+02"", + ""bike_group"" : [ ""TINK"" ], + ""K_a"": ""[-19, 29, -60, 29, 35, -121, -69, 93, 27, -122, 107, -127, -30, 74, 82, 12, 4, -20, 40, 16, 0, 0, 0, 0]"", + ""state"": ""occupied"", + ""real_hours"": ""0"", + ""station"" : ""7"", + ""start_time"": ""2020-04-07 16:55:17.786551+02"", + ""description"": ""Cargo Long"" + }"); + + Assert.AreEqual( + 238, + TextToTypeHelper.GetSeed(l_oInfo)[0]); + Assert.AreEqual( + 176, + TextToTypeHelper.GetSeed(l_oInfo)[1]); + } + + [Test] + public void TestGetSeedUserKeyAdminKey_Invalid() + { + var l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""total_price"": ""0.00"", + ""gps"" : { ""latitude"": ""47.6586133"", ""longitude"": ""9.16864"" }, + ""unit_price"": ""3.00"", + ""K_u"": ""[]"", + ""tariff_description"": {""name"" : ""TINK Basic""}, + ""end_time"": ""2020-04-07 16:55:18"", + ""K_seed"": ""[-18a, -80, 20, -90, 3, 69, 96, 4, -35, 75, -95, 102, 7, 121, -122, 15]"", + ""system"": ""Ilockit"", + ""bike"": ""16"", + ""computed_hours"": ""0"", + ""request_time"": ""2020-04-07 16:55:06.823436+02"", + ""bike_group"" : [ ""TINK"" ], + ""K_a"": ""{-19, 29, -60, 29, 35, -121, -69, 93, 27, -122, 107, -127, -30, 74, 82, 12, 4, -20, 40, 16, 0, 0, 0, 0}"", + ""state"": ""occupied"", + ""real_hours"": ""0"", + ""station"" : ""7"", + ""start_time"": ""2020-04-07 16:55:17.786551+02"", + ""description"": ""Cargo Long"" + }"); + + Assert.AreEqual( + 0, + TextToTypeHelper.GetSeed(l_oInfo).Length); + + Assert.AreEqual( + 0, + TextToTypeHelper.GetUserKey(l_oInfo).Length); + + Assert.AreEqual( + 0, + TextToTypeHelper.GetAdminKey(l_oInfo).Length); + } + + [Test] + public void TestGetBluetoothLockId_FromBikeInfo_Invalid() + { + var l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + }"); + + Assert.AreEqual(0, TextToTypeHelper.GetBluetoothLockId (l_oInfo)); + + l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""Ilockit_ID"": """" + }"); + + Assert.AreEqual(0, TextToTypeHelper.GetBluetoothLockId(l_oInfo)); + } + + [Test] + public void TestGetBluetoothLockId_FromBikeInfo() + { + var l_oInfo = JsonConvertRethrow.DeserializeObject(@" + { + ""Ilockit_ID"": ""ISHAREIT-132"" + }"); + + Assert.AreEqual( + 132, + TextToTypeHelper.GetBluetoothLockId(l_oInfo)); + } + + [Test] + public void TestGetBluetoothLockId_FromString_Invalid() + { + Assert.AreEqual(0, TextToLockItTypeHelper.GetBluetoothLockId((string)null)); + Assert.AreEqual(0, TextToLockItTypeHelper.GetBluetoothLockId("")); + + Assert.AreEqual(0, TextToLockItTypeHelper.GetBluetoothLockId("HubbaBubba")); + } + + [Test] + public void TestGetBluetoothLockId_FromString() + { + Assert.AreEqual( + 132, + TextToLockItTypeHelper.GetBluetoothLockId("ISHAREIT-132")); + + Assert.AreEqual( + 132, + TextToLockItTypeHelper.GetBluetoothLockId("ISHAREIT+132")); + } + + [Test] + public void TestGetCopriVersion() + { + var version = JsonConvertRethrow.DeserializeObject(@" + { + ""copri_version"": ""4.3.2.1"" + }"); + + Assert.That( + version.GetCopriVersion(), + Is.EqualTo(new Version(4,3,2,1))); + } + + [Test] + public void TestGetCopriVersion_Invald() + { + var version = JsonConvertRethrow.DeserializeObject(@" + { + ""copri_version"": ""hellO"" + }"); + + Assert.That( + () => version.GetCopriVersion(), + Throws.InstanceOf()); + } + + + [Test] + public void TestGetCopriVersion_Null() + { + + Assert.That( + () => TextToTypeHelper.GetCopriVersion(null), + Throws.InstanceOf()); + } + + [Test] + public void TestGetCopriVersion_NotContained() + { + var version = JsonConvertRethrow.DeserializeObject(@" + { + }"); + + Assert.That( + () => version.GetCopriVersion(), + Throws.InstanceOf()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Logging/TestLoggingDirectoryManager.cs b/TestTINKLib/Fixtures/ObjectTests/Logging/TestLoggingDirectoryManager.cs new file mode 100644 index 0000000..bd4ab9f --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Logging/TestLoggingDirectoryManager.cs @@ -0,0 +1,75 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using TINK.Model.Logging; + +namespace TestTINKLib.Fixtures.ObjectTests +{ + [TestFixture] + public class TestLoggingDirectoryManager + { + [Test] + public void TestConstructInvalidArgs() + { + Assert.Throws( + () => new LoggingDirectoryManager( + (name) => new List { "2018_02_06_22_18_00" /* oldest */, "2018_02_06_23_10_00" /*youngest*/ , "2018_02_06_22_19_00", "2018_02_06_22_20_00" }, + (name) => false, + (name) => { }, + (name) => { }, + "abc", + '\\', + 0)); + + Assert.Throws( + () => new LoggingDirectoryManager( + (name) => new List { "2018_02_06_22_18_00" /* oldest */, "2018_02_06_23_10_00" /*youngest*/ , "2018_02_06_22_19_00", "20180206222000" }, + (name) => false, + (name) => { }, + (name) => { }, + "", + '\\', + 3)); + } + + [Test] + public void TestDeleteObsoleteLogs() + { + var l_oDeletedFilesList = new List(); + + var l_oManger = new LoggingDirectoryManager( + (name) => new List { "2018_02_06_22_18_00" /* oldest */, "2018_02_06_23_10_00" /*youngest*/ , "2018_02_06_22_19_00", "20180206222000" }, + (name) => false, + (name) => { }, + (name) => { l_oDeletedFilesList.Add(name); }, + "abc", + '\\', + 3); + + l_oManger.DeleteObsoleteLogs(); + + Assert.AreEqual(2, l_oDeletedFilesList.Count); + Assert.IsTrue(l_oDeletedFilesList.Contains("2018_02_06_22_18_00")); + Assert.IsTrue(l_oDeletedFilesList.Contains("2018_02_06_22_19_00")); + } + + [Test] + public void TestDeleteObsoleteLogs_EmptyDirectory() + { + var l_oDeletedFilesList = new List(); + + var l_oManger = new LoggingDirectoryManager( + (name) => new List(), + (name) => false, + (name) => { }, + (name) => { l_oDeletedFilesList.Add(name); }, + "abc", + '\\', + 3); + + l_oManger.DeleteObsoleteLogs(); + + Assert.AreEqual(0, l_oDeletedFilesList.Count); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Map/TestMapPageFilter.cs b/TestTINKLib/Fixtures/ObjectTests/Map/TestMapPageFilter.cs new file mode 100644 index 0000000..8bd7c86 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Map/TestMapPageFilter.cs @@ -0,0 +1,43 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using TINK.Model; +using TINK.ViewModel.Map; + +namespace UITest.Fixtures.ObjectTests.Map +{ + [TestFixture] + public class TestMapPageFilter + { + [Test] + public void TestCurrentFilter_Empty() + { + var l_oFilter = new TinkKonradToggleViewModel(null); + Assert.IsEmpty(l_oFilter.CurrentFilter); + } + + [Test] + public void TestCurrentFilter() + { + var l_oFilter = new TinkKonradToggleViewModel(new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } })); + + Assert.AreEqual("TINK", l_oFilter.CurrentFilter); + + l_oFilter = new TinkKonradToggleViewModel(new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.Off }, { "Konrad", FilterState.On } })); + + Assert.AreEqual("Konrad", l_oFilter.CurrentFilter); + } + + [Test] + public void TestIsToggleVisible() + { + var l_oFilter = new TinkKonradToggleViewModel(new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On } })); + + Assert.IsFalse(l_oFilter.IsToggleVisible); + + l_oFilter = new TinkKonradToggleViewModel(new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.Off }, { "Konrad", FilterState.On } })); + + Assert.IsTrue(l_oFilter.IsToggleVisible); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Model/Repository/Request/TestRequestBuilderLoggedIn.cs b/TestTINKLib/Fixtures/ObjectTests/Model/Repository/Request/TestRequestBuilderLoggedIn.cs new file mode 100644 index 0000000..37da664 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Model/Repository/Request/TestRequestBuilderLoggedIn.cs @@ -0,0 +1,124 @@ +using NUnit.Framework; +using System; +using TINK.Repository.Request; + +namespace TestTINKLib.Fixtures.ObjectTests.Model.Repository.Request +{ + [TestFixture] + public class TestRequestBuilderLoggedIn + { + [Test] + public void TestUpateLockingStateGeolocationIsNull() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.UpateLockingState("12", null, lock_state.locked, 15.03), + Is.EqualTo("request=booking_update&bike=12&lock_state=locked&voltage=15.03&authcookie=MySessionCookieMyMeranctIt")); + } + + [Test] + public void TestUpateLockingStateGeolocationIsNullBatteryPercentageIsNAN() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.UpateLockingState("12", null, lock_state.locked, double.NaN), + Is.EqualTo("request=booking_update&bike=12&lock_state=locked&authcookie=MySessionCookieMyMeranctIt")); + } + + [Test] + public void TestUpateLockingStateGeolocation_AccuraycyNull() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.UpateLockingState("12", new LocationDto.Builder { Latitude = 21, Longitude = 17, Age = new TimeSpan(0, 0, 0, 0, 70) }.Build(), lock_state.locked, 12), + Is.EqualTo("request=booking_update&bike=12&gps=21,17&gps_age=0.07&lock_state=locked&voltage=12&authcookie=MySessionCookieMyMeranctIt")); + } + + [Test] + public void TestUpateLockingStateGeolocation_AccuraycyNullBatteryPercentageIsNAN() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.UpateLockingState("12", new LocationDto.Builder { Latitude = 21, Longitude = 17, Age = new TimeSpan(0, 0, 0, 0, 70) }.Build(), lock_state.locked, double.NaN), + Is.EqualTo("request=booking_update&bike=12&gps=21,17&gps_age=0.07&lock_state=locked&authcookie=MySessionCookieMyMeranctIt")); + } + + [Test] + public void TestUpateLockingStateGeolocation() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.UpateLockingState("12", new LocationDto.Builder { Latitude = 21, Longitude = 17, Accuracy = 5.7, Age = new TimeSpan(0, 0, 0, 0, 70) }.Build(), lock_state.locked, 98), + Is.EqualTo("request=booking_update&bike=12&gps=21,17&gps_accuracy=5.7&gps_age=0.07&lock_state=locked&voltage=98&authcookie=MySessionCookieMyMeranctIt")); + } + + [Test] + public void TestDoReturnGeolocationIsNull() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.DoReturn("12", null, null), + Is.EqualTo("request=booking_update&bike=12&authcookie=MySessionCookieMyMeranctIt&state=available&lock_state=locked")); + } + + [Test] + public void TestDoReturnGeolocation_AccuraycyNull() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.DoReturn( + "12", + new LocationDto.Builder { Latitude = 21, Longitude = 17, Age = new TimeSpan(0, 0, 0, 0, 70) }.Build(), + null), + Is.EqualTo("request=booking_update&bike=12&authcookie=MySessionCookieMyMeranctIt&state=available&gps=21,17&gps_age=0.07&lock_state=locked")); + } + + [Test] + public void TestDoReturnGeolocation() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.DoReturn( + "12", + new LocationDto.Builder { Latitude = 21, Longitude = 17, Accuracy = 5.7, Age = new TimeSpan(0, 0, 0, 0, 70) }.Build(), + null), + Is.EqualTo("request=booking_update&bike=12&authcookie=MySessionCookieMyMeranctIt&state=available&gps=21,17&gps_accuracy=5.7&gps_age=0.07&lock_state=locked")); + } + + [Test] + public void TestDoSubmitFeedback_Ok() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.DoSubmitFeedback("Radl22"), + Is.EqualTo("request=user_feedback&bike=Radl22&authcookie=MySessionCookieMyMeranctIt")); + } + + [Test] + public void TestDoSubmitFeedback_BikeBroken() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.DoSubmitFeedback("Cycle33", isBikeBroken: true), + Is.EqualTo("request=user_feedback&bike=Cycle33&bike_broken=1&authcookie=MySessionCookieMyMeranctIt")); + } + + [Test] + public void TestDoSubmitFeedback_Message() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.DoSubmitFeedback("Mühle", "Uno due tre"), + Is.EqualTo("request=user_feedback&bike=Mühle&message=Uno+due+tre&authcookie=MySessionCookieMyMeranctIt")); + } + + [Test] + public void TestDoSubmitFeedback_ErrorMessage() + { + var builder = new RequestBuilderLoggedIn("MyMeranctIt", "MySessionCookie"); + Assert.That( + builder.DoSubmitFeedback("bike12","Uno due tre", true), + Is.EqualTo("request=user_feedback&bike=bike12&bike_broken=1&message=Uno+due+tre&authcookie=MySessionCookieMyMeranctIt")); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Repository/Exception/TestBookingDeclinedException.cs b/TestTINKLib/Fixtures/ObjectTests/Repository/Exception/TestBookingDeclinedException.cs new file mode 100644 index 0000000..3fd1ace --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Repository/Exception/TestBookingDeclinedException.cs @@ -0,0 +1,39 @@ +using NUnit.Framework; +using TINK.Repository.Exception; + +namespace TestTINKLib.Fixtures.ObjectTests.Repository.Exception +{ + [TestFixture] + public class TestBookingDeclinedException + { + [Test] + public void TestIsBookingDeclined() + { + const string responseText = "OK: BOOKING_REQUEST declined. Max count of 8 occupied bikes has been reached"; + + BookingDeclinedException exception = null; + + Assert.That(() => BookingDeclinedException.IsBookingDeclined(responseText, out exception), + Is.EqualTo(true)); + + Assert.That(() => exception.MaxBikesCount, + Is.EqualTo(8)); + + } + + [Test] + public void TestIsBookingDeclined_InvalidNumber() + { + const string responseText = "OK: BOOKING_REQUEST declined. Max count of 8 occupied bikes has been reached"; + + BookingDeclinedException exception = null; + + Assert.That(() => BookingDeclinedException.IsBookingDeclined(responseText, out exception), + Is.EqualTo(true)); + + Assert.That(() => exception.MaxBikesCount, + Is.EqualTo(8)); + + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Repository/Exception/TestNoGPSDataException.cs b/TestTINKLib/Fixtures/ObjectTests/Repository/Exception/TestNoGPSDataException.cs new file mode 100644 index 0000000..71d846d --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Repository/Exception/TestNoGPSDataException.cs @@ -0,0 +1,36 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TINK.Repository.Exception; + +namespace TestTINKLib.Fixtures.ObjectTests.Repository.Exception +{ + [TestFixture] + public class TestNoGPSDataException + { + [Test] + public void TestIsNotAtStation() + { + const string responseText = "Failure 2245: No GPS data, state change forbidden."; + + NoGPSDataException exception = null; + + Assert.That(() => NoGPSDataException.IsNoGPSData(responseText, out exception), + Is.EqualTo(true)); + } + + [Test] + public void TestIsNotAtStation_InvalidNr() + { + const string responseText = "Failure 2248: No GPS data, state change forbidden."; + + NoGPSDataException exception = null; + + Assert.That(() => NoGPSDataException.IsNoGPSData(responseText, out exception), + Is.EqualTo(false)); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Repository/Exception/TestNotAtStationException.cs b/TestTINKLib/Fixtures/ObjectTests/Repository/Exception/TestNotAtStationException.cs new file mode 100644 index 0000000..bb47a29 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Repository/Exception/TestNotAtStationException.cs @@ -0,0 +1,37 @@ +using NUnit.Framework; +using TINK.Repository.Exception; + +namespace TestTINKLib.Fixtures.ObjectTests.Repository.Exception +{ + [TestFixture] + public class TestNotAtStationException + { + [Test] + public void TestIsNotAtStation() + { + const string responseText = "Failure 2178: bike 1545 out of GEO fencing. 15986 meter distance to next station 105. OK: bike 1545 locked confirmed"; + + NotAtStationException exception = null; + + Assert.That(() => NotAtStationException.IsNotAtStation(responseText, out exception), + Is.EqualTo(true)); + + Assert.That(() => exception.StationNr, + Is.EqualTo(105)); + + Assert.That(() => exception.Distance, + Is.EqualTo(15986)); + } + + [Test] + public void TestIsNotAtStation_InvalidNr() + { + const string responseText = "Failure 2177: bike 1545 out of GEO fencing. 15986 meter distance to next station 105. OK: bike 1545 locked confirmed"; + + NotAtStationException exception = null; + + Assert.That(() => NotAtStationException.IsNotAtStation(responseText, out exception), + Is.EqualTo(false)); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/Crypto/TestCryptoHelper.cs b/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/Crypto/TestCryptoHelper.cs new file mode 100644 index 0000000..ee40c00 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/Crypto/TestCryptoHelper.cs @@ -0,0 +1,85 @@ +using NUnit.Framework; +using System.Linq; +using System.Text; +using TINK.Services.BluetoothLock.Crypto; + +namespace TestTINKLib.Fixtures.ObjectTests.Services.BluetoothLock.Crypto +{ + [TestFixture] + public class TestCryptoHelper + { + /// + /// Ensures that decyption from haveltec- lib produces the same results than sharee lib. + /// + [Test] + public void Test_DecryptStringFromBytes_Aes() + { + // keyCopri (value copied from debugging session of sharing_ble_lib/ haveltec code) + var keyCopri = (new sbyte[] { -6, 53, 29, -112, 7, -83, -41, -7, 30, 45, -13, -2, -108, -29, -90, 71, 15, -74, -76, 32, 0, 0, 0, 0 }).Select(x => (byte)x).ToArray(); + + // Encrypted seed value from lock (value copied from debugging session of sharing_ble_lib/ haveltec code) + var seedLockEnc = (new sbyte[] { 50, 51, -40, 64, 42, 82, 97, -24, 20, -39, -15, 126, 119, -110, 47, -18 }).Select(x => (byte)x).ToArray(); + + // Decrypted seed value from lock (value copied from debugging session of sharing_ble_lib/ haveltec code) + var acces_key = (new sbyte[] { 19, -66, 55, 18, -106, -92, 70, -40, 117, -87, -19, 124, 19, 54, -18, -82 }).Select(x => (byte)x).ToArray(); + + var decrypt = new Cipher().Decrypt(keyCopri, seedLockEnc); + + Assert.IsTrue(acces_key.SequenceEqual(decrypt)); + } + + + [Test] + public void TestGetSeedLock() + { + // seed copri (value copied from debugging session of sharing_ble_lib/ haveltec code) + byte[] seedCopri = (new sbyte[] { -7, -69, 16, -52, 88, 38, -92, 82, -99, -79, 19, 16, -41, -127, 51, 24 }).Select(x => (byte)x).ToArray(); + + // keyCopri (value copied from debugging session of sharing_ble_lib/ haveltec code) + byte[] keyCopri = (new sbyte[] { -6, 53, 29, -112, 7, -83, -41, -7, 30, 45, -13, -2, -108, -29, -90, 71, 15, -74, -76, 32, 0, 0, 0, 0 }).Select(x => (byte)x).ToArray(); + + // Encrypted seed value from lock (value copied from debugging session of sharing_ble_lib/ haveltec code) + byte[] seedLockEnc = (new sbyte[] { 92, 80, -36, -2, 101, -31, -23, -43, 71, 62, 126, -70, 54, -53, -119, -56 }).Select(x => (byte)x).ToArray(); + + //// Decryped seed value? access values? from lock (value copied from debugging session of sharing_ble_lib/ haveltec code) + byte[] seedLockDec = (new sbyte[] { 62, -51, 96, -80, 7, -84, 48, -104, 47, 51, -22, -23, 30, -10, -88, -97 }).Select(x => (byte)x).ToArray(); + + var crypto = new AuthCryptoHelper( + seedLockEnc, + keyCopri, + null); + + var result = crypto.GetSeedLock(); + + Assert.IsTrue(seedLockDec.SequenceEqual(result)); + } + + + [Test] + public void TestGetAccessKeyEncrypted() + { + // seed copri (value copied from debugging session of sharing_ble_lib/ haveltec code) + byte[] seedCopri = (new sbyte[] { -7, -69, 16, -52, 88, 38, -92, 82, -99, -79, 19, 16, -41, -127, 51, 24 }).Select(x => (byte)x).ToArray(); + + // keyCopri (value copied from debugging session of sharing_ble_lib/ haveltec code) + byte[] keyCopri = (new sbyte[] { -6, 53, 29, -112, 7, -83, -41, -7, 30, 45, -13, -2, -108, -29, -90, 71, 15, -74, -76, 32, 0, 0, 0, 0 }).Select(x => (byte)x).ToArray(); + + // Encrypted seed value from lock (value copied from debugging session of sharing_ble_lib/ haveltec code) + byte[] seedLockEnc = (new sbyte[] { 92, 80, -36, -2, 101, -31, -23, -43, 71, 62, 126, -70, 54, -53, -119, -56 }).Select(x => (byte)x).ToArray(); + + // Decryped seed value? access values? from lock (value copied from debugging session of sharing_ble_lib/ haveltec code) + byte[] seedLockDec = (new sbyte[] { 62, -51, 96, -80, 7, -84, 48, -104, 47, 51, -22, -23, 30, -10, -88, -97 }).Select(x => (byte)x).ToArray(); + + var crypto = new AuthCryptoHelper( + seedLockEnc, + keyCopri, + null); + + var result = crypto.GetSeedLock(); + + Assert.AreEqual( + Encoding.UTF8.GetString(seedLockDec), + Encoding.UTF8.GetString(result)); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/Tdo/TestLockInfoAuthTdo.cs b/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/Tdo/TestLockInfoAuthTdo.cs new file mode 100644 index 0000000..6ac8d8c --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/Tdo/TestLockInfoAuthTdo.cs @@ -0,0 +1,24 @@ +using NUnit.Framework; +using TINK.Services.BluetoothLock.Tdo; + +namespace TestTINKLib.Fixtures.ObjectTests.Services.BluetoothLock.Tdo +{ + [TestFixture] + public class TestLockInfoAuthTdo + { + [Test] + public void TestCtor() + { + var auth = new LockInfoAuthTdo.Builder + { + K_seed = null, + K_u = null, + K_a = null, + }.Build(); + + Assert.That(auth.K_seed, Is.Not.Null); + Assert.That(auth.K_u, Is.Not.Null); + Assert.That(auth.K_a, Is.Not.Null); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/TestLockItBaseService.cs b/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/TestLockItBaseService.cs new file mode 100644 index 0000000..d4fd834 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/TestLockItBaseService.cs @@ -0,0 +1,92 @@ +using NSubstitute; +using NUnit.Framework; +using Plugin.BLE.Abstractions.Contracts; +using System; +using System.Threading.Tasks; +using TINK.Services.BluetoothLock.Tdo; +using TINK.Services.BluetoothLock.Exception; +using TINK.Services.BluetoothLock.BLE; +using DeviceState = Plugin.BLE.Abstractions.DeviceState; +using System.Threading; + +namespace TestTINKLib.Fixtures.ObjectTests.Services.BluetoothLock +{ + [TestFixture] + public class TestLockItBaseService + { + [Test] + public void TestConnect_Connected_InvalidArgs() + { + var device = Rhino.Mocks.MockRepository.GenerateStub(); + var adapter = Rhino.Mocks.MockRepository.GenerateStub(); + var cipher = Rhino.Mocks.MockRepository.GenerateStub(); + + Rhino.Mocks.RhinoMocksExtensions.Stub(device, x => x.State).Return(DeviceState.Disconnected); + + var exception = Assert.Throws( + () => { var lockIt = LockItEventBased.Authenticate(device, null, adapter, cipher).Result; }, + "If connected no auth is requied."); + + Assert.That(exception.InnerExceptions.Count > 0); + Assert.That(exception.InnerExceptions[0], Is.InstanceOf()); + + } + + [Test] + public void TestConnect_Connected() + { + var device = Substitute.For(); + var adapter = Substitute.For(); + var cipher = Substitute.For(); + var auth = Substitute.For(); + var lockControl = Substitute.For(); + + var authTdo = new LockInfoAuthTdo.Builder + { + Id = 12, + K_seed = new byte[] { (byte)'i', (byte)'V', (byte)'F', (byte)'m', (byte)'u', (byte)'T', (byte)'n', (byte)'K', (byte)'q', (byte)'E', (byte)'Y', (byte)'h', (byte)'m', (byte)'T', (byte)'l', (byte)'e' }, + K_u = new byte[16] + }.Build(); + + device.State.Returns(DeviceState.Connected); + device.GetServiceAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(lockControl)); + lockControl.GetCharacteristicAsync(Arg.Any()).Returns(Task.FromResult(auth)); + auth.WriteAsync(Arg.Any()).Returns(Task.FromResult(true)); // Write COPRI seed to lock + auth.ReadAsync(Arg.Any()).Returns(Task.FromResult(new byte[8])); // Read lock seed + cipher.Decrypt(Arg.Any(), Arg.Any()).Returns(new byte[3]); + cipher.Encrypt(Arg.Any(), Arg.Any()).Returns(new byte[16]); + auth.WriteAsync(Arg.Any()).Returns(Task.FromResult(true)); // Write COPRI seed to lock + + device.Name.Returns("Origin"); + + Assert.AreEqual( + "Origin", + LockItEventBased.Authenticate(device, authTdo, adapter, cipher).Result.Name, + "If connected no auth is requied."); + } + + [Test] + public void TestAuth() + { + var authTdo = new LockInfoAuthTdo.Builder { + Id = 12, + K_seed = new byte[] { (byte)'c', (byte)'b', (byte)'z', (byte)'b', (byte)'y', (byte)'I', (byte)'q', (byte)'j', (byte)'v', (byte)'L', (byte)'V', (byte)'I', (byte)'t', (byte)'C', (byte)'B', (byte)'I' }, + K_u = new byte[16] + }.Build(); + + var device = Rhino.Mocks.MockRepository.GenerateStub(); + var adapter = Rhino.Mocks.MockRepository.GenerateStub(); + var cipher = Rhino.Mocks.MockRepository.GenerateStub(); + + Rhino.Mocks.RhinoMocksExtensions.Stub(device, x => x.State).Return(DeviceState.Disconnected); + + // Use factory to create LockIt-object. + var exception = Assert.Throws( + () => { var lockIt = LockItEventBased.Authenticate(device, authTdo, adapter, cipher).Result; }, + "If connected no auth is requied."); + + Assert.That(exception.InnerExceptions.Count > 0); + Assert.That(exception.InnerExceptions[0], Is.InstanceOf()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/TestLockServiceSimulation.cs b/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/TestLockServiceSimulation.cs new file mode 100644 index 0000000..bdca9a7 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Services/BluetoothLock/TestLockServiceSimulation.cs @@ -0,0 +1,34 @@ +using System.Linq; +using System.Collections.Generic; +using NUnit.Framework; +using TINK.Model.Bike; +using TINK.Services.BluetoothLock; +using TINK.Services.BluetoothLock.Tdo; +using System; + +namespace TestTINKLib.Fixtures.ObjectTests.Service.LockService +{ + [TestFixture] + public class TestLockServiceSimulation + { + [Test] + public void TestUpdateSimulationInstance_Update() + { + var service = new LocksServiceInReach(); + + var bikes = new BikeCollection(new Dictionary() + { + { "42", new TINK.Model.Bike.BluetoothLock.BikeInfo("42", 1, new Guid(),new byte[] { 1, 4 }, new byte[] { 3, 4 }, new byte[] { 3, 4 }, DateTime.Now, "a@b", "1" , null /*operator uri*/) }, + { "43", new TINK.Model.Bike.BluetoothLock.BikeInfo("43", 3, new Guid(),new byte[] { 4, 4 }, new byte[] { 4, 7 }, new byte[] { 5, 4 }, DateTime.Now, "c@b", "1" , null /*operator uri*/) } + } + ); + + if (service is ILocksServiceFake serviceFake) + { + serviceFake.UpdateSimulation(bikes); + } + + Assert.AreEqual(2, service.GetLocksStateAsync(new List(), new TimeSpan(0, 0, 3)).Result.Count()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Services/TestLocksServicesContainerMutable.cs b/TestTINKLib/Fixtures/ObjectTests/Services/TestLocksServicesContainerMutable.cs new file mode 100644 index 0000000..aa9f48d --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Services/TestLocksServicesContainerMutable.cs @@ -0,0 +1,29 @@ +using NSubstitute; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using TINK.Model.Device; +using TINK.Services.BluetoothLock; +using TINK.Services.BluetoothLock.BLE; + +namespace TestTINKLib.Fixtures.ObjectTests.Services +{ + [TestFixture] + public class TestLocksServicesContainerMutable + { + [Test] + public void TestCtorCustomServices() + { + var ciper = NSubstitute.Substitute.For(); + var firstService = NSubstitute.Substitute.For(); + + Assert.That( + new LocksServicesContainerMutable( + firstService.GetType().FullName, + new HashSet() { firstService } + ).Count, + Is.EqualTo(1)); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Services/TestServicesContainerMutable.cs b/TestTINKLib/Fixtures/ObjectTests/Services/TestServicesContainerMutable.cs new file mode 100644 index 0000000..e9b7041 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Services/TestServicesContainerMutable.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using TINK.Services; + +namespace TestTINKLib.Fixtures.ObjectTests.Services +{ + [TestFixture] + public class TestServicesContainerMutable + { + [Test] + public void TestCtor() + { + var container = new ServicesContainerMutable(new List { new A(), new B() }, typeof(B).FullName); + Assert.That(container.Active.GetType().FullName, Is.EqualTo("TestTINKLib.Fixtures.ObjectTests.Services.TestServicesContainerMutable+B")); + } + + [Test] + public void TestCtorExceptionDupes() + { + Assert.That(() => new ServicesContainerMutable(new List { new A(), new B(), new A() }, typeof(B).FullName), Throws.InstanceOf()); + } + + [Test] + public void TestCtorExceptionActiveNotFound() + { + Assert.That(() => new ServicesContainerMutable(new List { new A(), new B() }, "C"), Throws.InstanceOf()); + } + + [Test] + public void TestSetActive() + { + var container = new ServicesContainerMutable(new List { new A(), new B() }, typeof(B).FullName); + container.SetActive(typeof(A).FullName); + Assert.That(container.Active.GetType().FullName, Is.EqualTo("TestTINKLib.Fixtures.ObjectTests.Services.TestServicesContainerMutable+A")); + } + + + private class A + { } + + private class B + { } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Settings/BluetoothLock/TestLockIt.cs b/TestTINKLib/Fixtures/ObjectTests/Settings/BluetoothLock/TestLockIt.cs new file mode 100644 index 0000000..1816042 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Settings/BluetoothLock/TestLockIt.cs @@ -0,0 +1,199 @@ +using NSubstitute; +using NUnit.Framework; +using Plugin.BLE.Abstractions; +using Plugin.BLE.Abstractions.Contracts; +using System; +using System.Threading.Tasks; +using TINK.Services.BluetoothLock.Tdo; +using TINK.Services.BluetoothLock.Exception; +using TINK.Services.BluetoothLock.BLE; +using System.Threading; + +namespace TestTINKLib.Fixtures.ObjectTests.Settings.BluetoothLock +{ + [TestFixture] + public class TestLockIt + { + [Test] + public void TestAuthenticate_InvalidSeed() + { + var device = Substitute.For(); + var adapter = Substitute.For(); + var ciper = Substitute.For(); + + var authInfo = new LockInfoAuthTdo.Builder { + K_seed = new byte[] { 0 }, + K_u = new byte[] { 1 } + }.Build(); + + device.State.Returns(DeviceState.Connected); + + device.Id.Returns(new Guid("00000000-0000-0000-0000-000000000001")); + + Assert.That(() => LockItEventBased.Authenticate(device, authInfo, adapter, ciper), Throws.InstanceOf()); + } + + [Test] + public void TestAuthenticate_InvalidAuthKey() + { + var device = Substitute.For(); + var adapter = Substitute.For(); + var ciper = Substitute.For(); + + var authInfo = new LockInfoAuthTdo.Builder + { + K_seed = new byte[] { (byte)'t', (byte)'q', (byte)'2', (byte)'n', (byte)'c', (byte)'A', (byte)'I', (byte)'v', (byte)'M', (byte)'t', (byte)'h', (byte)'g', (byte)'x', (byte)'a', (byte)'z', (byte)'B' }, + K_u = new byte[] { } + }.Build(); + + device.State.Returns(DeviceState.Connected); + device.Id.Returns(new Guid("00000000-0000-0000-0000-000000000001")); + + Assert.That(async () => { await LockItEventBased.Authenticate(device, authInfo, adapter, ciper); }, Throws.InstanceOf()); + } + + [Test] + public void TestAuthenticate_Invalidated() + { + var device = Substitute.For(); + var adapter = Substitute.For(); + var cipher = Substitute.For(); + var auth = Substitute.For(); + var lockControl = Substitute.For(); + var state = Substitute.For(); + var activateLock = Substitute.For(); + + var authInfo = new LockInfoAuthTdo.Builder + { + K_seed = new byte[] { (byte)'z', (byte)'D', (byte)'G', (byte)'x', (byte)'q', (byte)'M', (byte)'f', (byte)'A', (byte)'F', (byte)'q', (byte)'g', (byte)'N', (byte)'V', (byte)'r', (byte)'N', (byte)'Y' }, + K_u = new byte[] { 1 } + }.Build(); + + // Calls related to Authenticate functionality. + device.State.Returns(DeviceState.Connected); + device.Id.Returns(new Guid("0000f00d-1212-efde-1523-785fef13d123")); + device.GetServiceAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(lockControl)); + lockControl.GetCharacteristicAsync(new Guid("0000baab-1212-efde-1523-785fef13d123")).Returns(Task.FromResult(auth)); + auth.WriteAsync(Arg.Any()).Returns(Task.FromResult(true)); + auth.ReadAsync(Arg.Any()).Returns(Task.FromResult(new byte[8])); + cipher.Decrypt(Arg.Any(), Arg.Any()).Returns(new byte[3]); + cipher.Encrypt(Arg.Any(), Arg.Any()).Returns(new byte[16]); + auth.WriteAsync(Arg.Any()).Returns(true); + device.State.Returns(DeviceState.Connected); + device.Id.Returns(new Guid("00000000-0000-0000-0000-000000000001")); + + // Call authenticate to invalidate seed + var lockIt = LockItEventBased.Authenticate(device, authInfo, adapter, cipher).Result; + + // Authenticate again to + var execption = Assert.Throws(() => { var dummy = LockItEventBased.Authenticate(device, authInfo, adapter, cipher).Result; }); + + Assert.That( + execption.InnerExceptions[0].Message, + Does.Contain ("Seed 122,68,71,120,113,77,102,65,70,113,103,78,86,114,78,89 was already used.")); + + } + + [Test] + public void TestAuthenticate_GetAuthCharacteristicThrowsException() + { + var device = Substitute.For(); + var adapter = Substitute.For(); + var cipher = Substitute.For(); + var auth = Substitute.For(); + var lockControl = Substitute.For(); + var state = Substitute.For(); + var activateLock = Substitute.For(); + + var authInfo = new LockInfoAuthTdo.Builder + { + K_seed = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, + K_u = new byte[] { 1 } + }.Build(); + + // Calls related to Authenticate functionality. + device.State.Returns(DeviceState.Connected); + device.Id.Returns(new Guid("0000f00d-1212-efde-1523-785fef13d123")); + device.GetServiceAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(lockControl)); + lockControl.GetCharacteristicAsync(new Guid("0000baab-1212-efde-1523-785fef13d123")).Returns(Task.FromResult((ICharacteristic)null)); + + // Authenticate again to + Assert.That(() => LockItEventBased.Authenticate(device, authInfo, adapter, cipher), Throws.InstanceOf()); + } + + [Test] + public async Task TestAuthenticate_GetLockState_Closed() + { + var device = Substitute.For(); + var adapter = Substitute.For(); + var cipher = Substitute.For(); + var auth = Substitute.For(); + var lockControl = Substitute.For(); + var state = Substitute.For(); + + var authInfo = new LockInfoAuthTdo.Builder + { + K_seed = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, + K_u = new byte[] { 1 } + }.Build(); + + // Calls related to Authenticate functionality. + device.State.Returns(DeviceState.Connected); + device.Id.Returns(new Guid("0000f00d-1212-efde-1523-785fef13d123")); + device.GetServiceAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(lockControl)); + lockControl.GetCharacteristicAsync(new Guid("0000baab-1212-efde-1523-785fef13d123")).Returns(Task.FromResult(auth)); + auth.WriteAsync(Arg.Any()).Returns(Task.FromResult(true)); + auth.ReadAsync(Arg.Any()).Returns(Task.FromResult(new byte[8])); + cipher.Decrypt(Arg.Any(), Arg.Any()).Returns(new byte[3]); + cipher.Encrypt(Arg.Any(), Arg.Any()).Returns(new byte[16]); + auth.WriteAsync(Arg.Any()).Returns(true); + device.State.Returns(DeviceState.Connected); + device.Id.Returns(new Guid("00000000-0000-0000-0000-000000000001")); + // Calls related to get lock state. + + // Call authenticate to invalidate seed + lockControl.GetCharacteristicAsync(new Guid("0000baaa-1212-efde-1523-785fef13d123")).Returns(Task.FromResult(state)); + state.ReadAsync(Arg.Any()).Returns(new byte[] { 1 }); + + var lockIt = LockItEventBased.Authenticate(device, authInfo, adapter, cipher).Result; + + Assert.That((await lockIt.GetLockStateAsync()).State, Is.EqualTo(LockitLockingState.Closed)); + } + + [Test] + public void TestAuthenticate_GetLockState_GetStateCharacteristicThrowsException() + { + var device = Substitute.For(); + var adapter = Substitute.For(); + var cipher = Substitute.For(); + var auth = Substitute.For(); + var lockControl = Substitute.For(); + + var authInfo = new LockInfoAuthTdo.Builder + { + K_seed = new byte[] { (byte)'y', (byte)'D', (byte)'G', (byte)'x', (byte)'q', (byte)'M', (byte)'f', (byte)'A', (byte)'F', (byte)'q', (byte)'g', (byte)'N', (byte)'V', (byte)'r', (byte)'N', (byte)'Y' }, + K_u = new byte[] { 1 } + }.Build(); + + // Calls related to Authenticate functionality. + device.State.Returns(DeviceState.Connected); + device.Id.Returns(new Guid("0000f00d-1212-efde-1523-785fef13d123")); + device.GetServiceAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(lockControl)); + lockControl.GetCharacteristicAsync(new Guid("0000baab-1212-efde-1523-785fef13d123")).Returns(Task.FromResult(auth)); + auth.WriteAsync(Arg.Any()).Returns(Task.FromResult(true)); + auth.ReadAsync(Arg.Any()).Returns(Task.FromResult(new byte[8])); + cipher.Decrypt(Arg.Any(), Arg.Any()).Returns(new byte[3]); + cipher.Encrypt(Arg.Any(), Arg.Any()).Returns(new byte[16]); + auth.WriteAsync(Arg.Any()).Returns(true); + device.State.Returns(DeviceState.Connected); + device.Id.Returns(new Guid("00000000-0000-0000-0000-000000000001")); + + // Calls related to get lock state. + lockControl.GetCharacteristicAsync(new Guid("0000baaa-1212-efde-1523-785fef13d123")).Returns(Task.FromResult((ICharacteristic)null)); + + var lockIt = LockItEventBased.Authenticate(device, authInfo, adapter, cipher).Result; + + Assert.That(async () => (await lockIt.GetLockStateAsync()).State, Throws.InstanceOf()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Settings/TestGroupFilterSettings.cs b/TestTINKLib/Fixtures/ObjectTests/Settings/TestGroupFilterSettings.cs new file mode 100644 index 0000000..05340f9 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Settings/TestGroupFilterSettings.cs @@ -0,0 +1,23 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using TINK.Model; +using TINK.ViewModel.Settings; + +namespace TestTINKLib.Fixtures.ObjectTests.Settings +{ + [TestFixture] + public class TestGroupFilterSettings + { + [Test] + public void TestDoFilter() + { + var l_oFilter = new GroupFilterSettings(new Dictionary { { "Konrad", FilterState.Off }, { "TINK", FilterState.On }, { "FutureBikeType", FilterState.On} }); + + var l_oResult = l_oFilter.DoFilter(new List { "Konrad", "TINK" }); + + Assert.AreEqual(1, l_oResult.ToList().Count); + Assert.AreEqual("TINK", l_oResult.ToList()[0]); + } + } +} \ No newline at end of file diff --git a/TestTINKLib/Fixtures/ObjectTests/Settings/TestPollingParameters.cs b/TestTINKLib/Fixtures/ObjectTests/Settings/TestPollingParameters.cs new file mode 100644 index 0000000..993d81d --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Settings/TestPollingParameters.cs @@ -0,0 +1,41 @@ +using NUnit.Framework; +using System; +using TINK.Settings; + +namespace TestTINKLib.Fixtures.ObjectTests.Settings +{ + [TestFixture] + public class TestPollingParameters + { + [Test] + public void TestConstruct() + { + Assert.IsTrue(new PollingParameters(new TimeSpan(0, 0, 11), true).IsActivated); + Assert.AreEqual(11, (new PollingParameters(new TimeSpan(0, 0, 11), true).Periode.TotalSeconds)); + } + + [Test] + public void TestEquals() + { + Assert.IsTrue((new PollingParameters(new TimeSpan(0, 0, 11), true)) == (new PollingParameters(new TimeSpan(0, 0, 11), true))); + Assert.IsFalse((new PollingParameters(new TimeSpan(0, 0, 11), false)) == (new PollingParameters(new TimeSpan(0, 0, 11), true))); + Assert.IsFalse((new PollingParameters(new TimeSpan(0, 0, 12), true)) == (new PollingParameters(new TimeSpan(0, 0, 11), true))); + } + + [Test] + public void TestUnequals() + { + Assert.IsFalse((new PollingParameters(new TimeSpan(0, 0, 11), true)) != (new PollingParameters(new TimeSpan(0, 0, 11), true))); + Assert.IsTrue((new PollingParameters(new TimeSpan(0, 0, 11), false)) != (new PollingParameters(new TimeSpan(0, 0, 11), true))); + Assert.IsTrue((new PollingParameters(new TimeSpan(0, 0, 12), true)) != (new PollingParameters(new TimeSpan(0, 0, 11), true))); + } + + [Test] + public void TestToString() + { + Assert.AreEqual( + "Polling is on=True, polling interval=11[sec].", + (new PollingParameters(new TimeSpan(0, 0, 11), true).ToString())); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Settings/TestSettings.cs b/TestTINKLib/Fixtures/ObjectTests/Settings/TestSettings.cs new file mode 100644 index 0000000..b466e06 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Settings/TestSettings.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; +using Serilog.Events; +using System; +using TINK.Model; +using TINK.Model.Services.CopriApi.ServerUris; +using TINK.Settings; + +namespace TestTINKLib.Fixtures.ObjectTests.Settings +{ + [TestFixture] + public class TestSettings + { + [Test] + public void TestConstructDefaults() + { + var settings = new TINK.Model.Settings.Settings(); + + Assert.AreEqual(LogEventLevel.Error, settings.MinimumLogEventLevel); + + Assert.AreEqual(GroupFilterHelper.GetSettingsFilterDefaults, settings.GroupFilterSettings); + + Assert.AreEqual(GroupFilterHelper.GetMapPageFilterDefaults, settings.GroupFilterMapPage); + + Assert.AreEqual(new CopriServerUriList().ActiveUri, settings.ActiveUri); + + Assert.AreEqual(PollingParameters.Default, settings.PollingParameters); + + Assert.IsTrue( + settings.CenterMapToCurrentLocation, + "Center to map for sharee.bike because bt- locks require location info."); + } + + [Test] + public void TestCtorTink() + { + var settings = new TINK.Model.Settings.Settings(activeUri: new Uri(CopriServerUriList.TINK_LIVE)); + Assert.IsFalse( + settings.CenterMapToCurrentLocation, + "Do not center to current location for TINK."); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/State/TestStateBookedInfo.cs b/TestTINKLib/Fixtures/ObjectTests/State/TestStateBookedInfo.cs new file mode 100644 index 0000000..680039f --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/State/TestStateBookedInfo.cs @@ -0,0 +1,30 @@ +using NUnit.Framework; +using System; +using TINK.Model.State; + + +namespace TestTINKLib +{ + + [TestFixture] + public class TestStateBookedInfo + { + [Test] + public void TestConstruct() + { + var l_oBookedInfo = new StateOccupiedInfo(new DateTime(2017, 09, 20, 23, 05, 0), "Heinz@mueller", "17 xxb"); + + Assert.AreEqual( + new DateTime(2017, 09, 20, 23, 05, 0), + l_oBookedInfo.From); + + Assert.AreEqual( + "Heinz@mueller", + l_oBookedInfo.MailAddress); + + Assert.AreEqual( + "17 xxb", + l_oBookedInfo.Code); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/State/TestStateDisposableInfo.cs b/TestTINKLib/Fixtures/ObjectTests/State/TestStateDisposableInfo.cs new file mode 100644 index 0000000..c7124c6 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/State/TestStateDisposableInfo.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; +using TINK.Model.State; + + +namespace TestTINKLib +{ + + [TestFixture] + public class TestStateDisposableInfo + { + [Test] + public void TestConstruct() + { + Assert.AreEqual(InUseStateEnum.Disposable, new StateAvailableInfo().Value); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/State/TestStateInfoMutable.cs b/TestTINKLib/Fixtures/ObjectTests/State/TestStateInfoMutable.cs new file mode 100644 index 0000000..1bac799 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/State/TestStateInfoMutable.cs @@ -0,0 +1,188 @@ +using NUnit.Framework; +using Rhino.Mocks; +using System; +using System.Collections.Generic; +using TINK.Model.State; + + +namespace TestTINKLib +{ + + [TestFixture] + public class TestStateInfo + { + [Test] + public void TestConstruct() + { + var l_oInUseState = new StateInfoMutable(() => new DateTime(2017, 09, 20, 23, 20, 0)); + + // Verify initial values of properties. + Assert.AreEqual(InUseStateEnum.Disposable, l_oInUseState.Value, "Default state is available"); + Assert.AreEqual("Disposable", l_oInUseState.ToString()); + Assert.IsNull(l_oInUseState.RemainingTime); + Assert.IsNull(l_oInUseState.From); + Assert.IsNull(l_oInUseState.Code); + } + + [Test] + public void TestConstructCopy() + { + // State is available. + var l_oSource = MockRepository.GenerateStub(); + l_oSource.Stub(x => x.Value).Return(InUseStateEnum.Disposable); + + var l_oState = new StateInfoMutable(p_oState: l_oSource); + + Assert.AreEqual(InUseStateEnum.Disposable, l_oState.Value); + Assert.IsNull(l_oState.RemainingTime); + Assert.IsNull(l_oState.From); + Assert.IsNull(l_oState.Code); + + // State is requested. + l_oSource = MockRepository.GenerateStub(); + l_oSource.Stub(x => x.Value).Return(InUseStateEnum.Reserved); + l_oSource.Stub(x => x.From).Return(new DateTime(2018, 1, 4, 17, 26, 0)); + l_oSource.Stub(x => x.MailAddress).Return("who@the"); + l_oSource.Stub(x => x.Code).Return("323"); + + l_oState = new StateInfoMutable(() => new DateTime(2018, 1, 4, 17, 30, 1), l_oSource); + + Assert.AreEqual(InUseStateEnum.Reserved, l_oState.Value); + Assert.AreEqual(new TimeSpan(0, 10, 59), l_oState.RemainingTime); + Assert.AreEqual(new DateTime(2018, 1, 4, 17, 26, 0), l_oState.From); + Assert.AreEqual("who@the", l_oState.MailAddress); + Assert.AreEqual("323", l_oState.Code); + + // State is booked. + l_oSource = MockRepository.GenerateStub(); + l_oSource.Stub(x => x.Value).Return(InUseStateEnum.Booked); + l_oSource.Stub(x => x.From).Return(new DateTime(2018, 1, 4, 17, 00, 0)); + l_oSource.Stub(x => x.MailAddress).Return("who@the"); + l_oSource.Stub(x => x.Code).Return("323"); + + l_oState = new StateInfoMutable(() => new DateTime(2018, 1, 4, 17, 30, 1), l_oSource); + + Assert.AreEqual(InUseStateEnum.Booked, l_oState.Value); + Assert.IsNull(l_oState.RemainingTime); + Assert.AreEqual(new DateTime(2018, 1, 4, 17, 00, 0), l_oState.From); + Assert.AreEqual("who@the", l_oState.MailAddress); + Assert.AreEqual("323", l_oState.Code); + } + + [Test] + public void TestDoUpdate() + { + var l_oReservedAt = new DateTime(2017, 09, 20, 22, 01, 00); + var l_oDateTimeMock = new DateTimeMocker(new List { + l_oReservedAt, // Booking time + l_oReservedAt.Add(new TimeSpan(0, 4, 0)), // Reserved for 4 mns + l_oReservedAt.Add(new TimeSpan(0, 16, 0)) // Time elapsed since booking 16 mns + }); + + var l_oStateInfo = new StateInfoMutable(l_oDateTimeMock.GetDateTime); + + // Update state from Copri. + l_oStateInfo.Load( + InUseStateEnum.Reserved, // Copri acknowledges state reserved. + l_oDateTimeMock.GetDateTime(), + "heiz@mustermann"); // Owner from Copri. + + // Invoke first update (after simulated 4mns) + l_oStateInfo.UpdateOnTimeElapsed(); + Assert.AreEqual(InUseStateEnum.Reserved, l_oStateInfo.Value); + Assert.AreEqual(11, l_oStateInfo.RemainingTime.Value.Minutes); + Assert.AreEqual(new DateTime(2017, 09, 20, 22, 01, 00), l_oStateInfo.From.Value); + Assert.AreEqual("heiz@mustermann", l_oStateInfo.MailAddress); + Assert.IsNull(l_oStateInfo.Code); + + // Invoke second update (after simulated 16 mns) + l_oStateInfo.UpdateOnTimeElapsed(); + Assert.AreEqual(InUseStateEnum.Disposable, l_oStateInfo.Value); + Assert.IsNull(l_oStateInfo.RemainingTime); + Assert.IsNull(l_oStateInfo.From); + Assert.IsNull(l_oStateInfo.MailAddress); + Assert.IsNull(l_oStateInfo.Code); + } + + [Test] + public void TestUpdateFromWebserver() + { + Func l_oNowMock = () => new DateTime(2017, 09, 21, 23, 40, 0); + var l_oStateInfo = new StateInfoMutable(l_oNowMock); + + l_oStateInfo.Load(InUseStateEnum.Booked, new DateTime(2017, 09, 21, 23, 30, 0), "heiz@mustermann", "21"); + + Assert.AreEqual(InUseStateEnum.Booked, l_oStateInfo.Value); + Assert.AreEqual("Booked", l_oStateInfo.ToString()); + Assert.IsNull(l_oStateInfo.RemainingTime); + Assert.AreEqual(new DateTime(2017, 09, 21, 23, 30, 0), l_oStateInfo.From.Value); + Assert.AreEqual("heiz@mustermann", l_oStateInfo.MailAddress); + Assert.AreEqual("21", l_oStateInfo.Code); + + DateTime FROM = new DateTime(2017, 09, 21, 23, 35, 0); + l_oStateInfo.Load(InUseStateEnum.Reserved, FROM, "heiz@mustermann", "22"); + + // Verify initial values of properties. + Assert.AreEqual(InUseStateEnum.Reserved, l_oStateInfo.Value); + Assert.AreEqual("Reserved", l_oStateInfo.ToString()); + Assert.AreEqual(new TimeSpan(0, 15, 0).Subtract(l_oNowMock().Subtract(FROM)).Minutes, l_oStateInfo.RemainingTime.Value.Minutes); + Assert.AreEqual(FROM, l_oStateInfo.From); + Assert.AreEqual("heiz@mustermann", l_oStateInfo.MailAddress); + Assert.AreEqual("22", l_oStateInfo.Code); + + l_oStateInfo.Load(InUseStateEnum.Disposable, new DateTime(1970, 1, 1, 0, 0, 0), "heiz@mustermann", "unused"); + + // Verify initial values of properties. + Assert.AreEqual(InUseStateEnum.Disposable, l_oStateInfo.Value); + Assert.AreEqual("Disposable", l_oStateInfo.ToString()); + Assert.IsNull(l_oStateInfo.RemainingTime); + Assert.IsNull(l_oStateInfo.From); + Assert.IsNull( l_oStateInfo.MailAddress); + Assert.IsNull(l_oStateInfo.Code); + } + + [Test] + public void TestLoad() + { + var l_oState = new StateInfoMutable( + () => new DateTime(2018, 01, 03, 21, 14, 0)); // Now + Assert.AreEqual(InUseStateEnum.Disposable, l_oState.Value); + Assert.IsNull(l_oState.From); + Assert.IsNull(l_oState.RemainingTime); + Assert.IsNull(l_oState.MailAddress); + Assert.IsNull(l_oState.Code); + + // Construct requested state object. + var l_oSource = new StateInfo( + () => new DateTime(2018, 01, 03, 21, 53, 0), + new DateTime(2018, 01, 03, 21, 13, 0), // Requested from + "a@b", + "123"); + l_oState.Load(l_oSource); + Assert.AreEqual(InUseStateEnum.Reserved, l_oState.Value); + Assert.AreEqual(new DateTime(2018, 01, 03, 21, 13, 0), l_oState.From); + Assert.AreEqual(14, l_oState.RemainingTime.Value.Minutes); + Assert.AreEqual("a@b", l_oState.MailAddress); + Assert.AreEqual("123", l_oState.Code); + + + // Construct booked state object. + l_oSource = new StateInfo(new DateTime(2018, 01, 03, 21, 37, 0), "a@b", "123"); + l_oState.Load(l_oSource); + Assert.AreEqual(InUseStateEnum.Booked, l_oState.Value); + Assert.AreEqual(new DateTime(2018, 01, 03, 21, 37, 0), l_oState.From); + Assert.IsNull(l_oState.RemainingTime); + Assert.AreEqual("a@b", l_oState.MailAddress); + Assert.AreEqual("123", l_oState.Code); + + // Construct disposable object + l_oSource = new StateInfo(); + l_oState.Load(l_oSource); + Assert.AreEqual(InUseStateEnum.Disposable, l_oState.Value); + Assert.IsNull(l_oState.From); + Assert.IsNull(l_oState.RemainingTime); + Assert.IsNull(l_oState.MailAddress); + Assert.IsNull(l_oState.Code); + } + } +} \ No newline at end of file diff --git a/TestTINKLib/Fixtures/ObjectTests/State/TestStateInfoSerializeJSON.cs b/TestTINKLib/Fixtures/ObjectTests/State/TestStateInfoSerializeJSON.cs new file mode 100644 index 0000000..a20de51 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/State/TestStateInfoSerializeJSON.cs @@ -0,0 +1,213 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using TINK.Model.State; + + +namespace TestTINKLib.Fixtures.State +{ + + [TestFixture] + public class TestStateInfoSerializeJSON + { + [Test] + public void TestSerializeJSON_Disposable() + { + string l_strJSONDetected; + { + // Create object to test. + var l_oInUseState = new StateInfoMutable(() => new DateTime(2017, 09, 20, 23, 20, 0)); + + // Serialize object and verify json + l_strJSONDetected = JsonConvert.SerializeObject(l_oInUseState, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Auto, + NullValueHandling = NullValueHandling.Ignore, + }); + + const string EXPECTED = @" + { + ""StateInfoObject"": + { + ""$type"":""TINK.Model.State.StateAvailableInfo, TINKLib"" + } + }"; + Assert.AreEqual( + TestHelper.PrepareXmlForStringCompare(EXPECTED.Replace("\n", string.Empty).Replace("\r", string.Empty)), + TestHelper.PrepareXmlForStringCompare(l_strJSONDetected.Replace("\n", string.Empty).Replace("\r", string.Empty))); + } + + { + // Deserialize object + var l_oInUseStateTarget = JsonConvert.DeserializeObject(l_strJSONDetected, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); + + // Verify state. + Assert.AreEqual(InUseStateEnum.Disposable, l_oInUseStateTarget.Value); + Assert.AreEqual("Disposable", l_oInUseStateTarget.ToString()); + Assert.IsNull(l_oInUseStateTarget.RemainingTime); + Assert.IsNull(l_oInUseStateTarget.From); + Assert.IsNull(l_oInUseStateTarget.MailAddress); + Assert.IsNull(l_oInUseStateTarget.Code); + } + } + [Test] + public void TestSerializeJSON_Booked() + { + // Create object to test. + var l_oInUseState = new StateInfoMutable( + () => new DateTime(2017, 11, 18, 23, 20, 0) // Mocked time stamp returned when StateInfo- object is crated + ); + + l_oInUseState.Load( + InUseStateEnum.Booked, + new DateTime(2017, 11, 18, 23, 19, 0), // Time booked at + "heiz@mustermann", + "173"); // Code + + Assert.AreEqual(InUseStateEnum.Booked, l_oInUseState.Value); + Assert.AreEqual("heiz@mustermann", l_oInUseState.MailAddress); + Assert.AreEqual(new DateTime(2017, 11, 18, 23, 19, 0), l_oInUseState.From); + Assert.AreEqual("173", l_oInUseState.Code); + + // Verify json + const string EXPECTED = @" + { + ""StateInfoObject"": + { + ""$type"":""TINK.Model.State.StateOccupiedInfo, TINKLib"", + ""From"":""2017 - 11 - 18T23: 19:00"", + ""MailAddress"":""heiz @mustermann"",""Code"":""173"" + } + }"; + + var l_oDetected = JsonConvert.SerializeObject(l_oInUseState, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Auto, + NullValueHandling = NullValueHandling.Ignore, + }); + + Assert.AreEqual( + TestHelper.PrepareXmlForStringCompare(EXPECTED).Replace("\n", string.Empty).Replace("\r", string.Empty), + TestHelper.PrepareXmlForStringCompare(l_oDetected).Replace("\n", string.Empty).Replace("\r", string.Empty)); + + // Deserialize object an verify state. + var l_oStateTarget = JsonConvert.DeserializeObject(l_oDetected, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); + // Verify state. + Assert.AreEqual(InUseStateEnum.Booked, l_oStateTarget.Value); + Assert.AreEqual(new DateTime(2017, 11, 18, 23, 19, 0), l_oInUseState.From); + Assert.AreEqual("heiz@mustermann", l_oStateTarget.MailAddress); + Assert.AreEqual("173", l_oInUseState.Code); + } + + [Test] + public void TestSerializeJSON_Reserved_NoCode() + { + string l_strJSONDetected; + + { + // Create object to test. + var l_oInUseState = new StateInfoMutable(() => new DateTime(2017, 09, 21, 23, 20, 0)); + l_oInUseState.Load( + InUseStateEnum.Reserved, + new DateTime(2017, 09, 21, 23, 20, 0), + "heiz@mustermann"); + + Assert.AreEqual(InUseStateEnum.Reserved, l_oInUseState.Value); + Assert.AreEqual("heiz@mustermann", l_oInUseState.MailAddress); + Assert.AreEqual(new DateTime(2017, 09, 21, 23, 20, 0), l_oInUseState.From); + Assert.AreEqual(new TimeSpan(0, 15, 0), l_oInUseState.RemainingTime); + Assert.IsNull(l_oInUseState.Code); + + l_strJSONDetected = JsonConvert.SerializeObject(l_oInUseState, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Auto, + NullValueHandling = NullValueHandling.Ignore, + }); + + // Verify json + const string EXPECTED = @" + { + ""StateInfoObject"": + { + ""$type"":""TINK.Model.State.StateRequestedInfo, TINKLib"", + ""From"":""2017 - 09 - 21T23: 20:00"", + ""MailAddress"":""heiz @mustermann"" + } + }"; + + Assert.AreEqual( + TestHelper.PrepareXmlForStringCompare(EXPECTED.Replace("\n", string.Empty).Replace("\r", string.Empty)), + TestHelper.PrepareXmlForStringCompare(l_strJSONDetected.Replace("\n", string.Empty).Replace("\r", string.Empty))); + } + + { + // Deserialize object an verify state. + var l_oStateTarget = JsonConvert.DeserializeObject(l_strJSONDetected, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); + // Verify state. + Assert.AreEqual(InUseStateEnum.Reserved, l_oStateTarget.Value); + Assert.AreEqual("heiz@mustermann", l_oStateTarget.MailAddress); + Assert.AreEqual(new DateTime(2017, 09, 21, 23, 20, 0), l_oStateTarget.From); + Assert.IsNull(l_oStateTarget.RemainingTime, "If deserialized date time provider is DateTime.Now. With a from- value of 2017-11- ... there is no more time remaining."); + Assert.IsNull(l_oStateTarget.Code); + } + } + + [Test] + public void TestSerializeJSON_Reserved() + { + string l_strJSONDetected; + + { + Func l_oNow = () => new DateTime(2017, 11, 18, 23, 18, 0); + // Create object to test. + var l_oInUseState = new StateInfoMutable(l_oNow); + + DateTime l_oFrom = new DateTime(2017, 11, 18, 23, 18, 0); + l_oInUseState.Load( + InUseStateEnum.Reserved, + l_oFrom, + "z@C", + "01815A"); + + Assert.AreEqual(InUseStateEnum.Reserved, l_oInUseState.Value); + Assert.AreEqual("z@C", l_oInUseState.MailAddress); + Assert.AreEqual(new DateTime(2017, 11, 18, 23, 18, 0), l_oInUseState.From); + Assert.AreEqual((new TimeSpan(0, 15, 0)).Subtract(l_oNow().Subtract(l_oFrom)), l_oInUseState.RemainingTime); + Assert.AreEqual("01815A", l_oInUseState.Code); + + l_strJSONDetected = JsonConvert.SerializeObject(l_oInUseState, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Auto, + NullValueHandling = NullValueHandling.Ignore, + }); + + // Verify json + const string EXPECTED = @" + { + ""StateInfoObject"": + { + ""$type"":""TINK.Model.State.StateRequestedInfo, TINKLib"", + ""From"":""2017 - 11 - 18T23: 18:00"", + ""MailAddress"":""z @C"", + ""Code"":""01815A"" + } + }"; + + Assert.AreEqual( + TestHelper.PrepareXmlForStringCompare(EXPECTED.Replace("\n", string.Empty).Replace("\r", string.Empty)), + TestHelper.PrepareXmlForStringCompare(l_strJSONDetected.Replace("\n", string.Empty).Replace("\r", string.Empty))); + } + + { + // Deserialize object an verify state. + var l_oStateTarget = JsonConvert.DeserializeObject(l_strJSONDetected, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); + // Verify state. + Assert.AreEqual(InUseStateEnum.Reserved, l_oStateTarget.Value); + Assert.AreEqual("z@C", l_oStateTarget.MailAddress); + Assert.AreEqual(new DateTime(2017, 11, 18, 23, 18, 0), l_oStateTarget.From); + Assert.IsNull(l_oStateTarget.RemainingTime, "If deserialized date time provider is DateTime.Now. With a from- value of 2017-11- ... there is no more time remaining."); + Assert.AreEqual("01815A", l_oStateTarget.Code); + } + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/State/TestStateRequestedInfo.cs b/TestTINKLib/Fixtures/ObjectTests/State/TestStateRequestedInfo.cs new file mode 100644 index 0000000..bd0df86 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/State/TestStateRequestedInfo.cs @@ -0,0 +1,90 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using TINK.Model.State; + + +namespace TestTINKLib +{ + + [TestFixture] + public class TestStateRequestedInfo + { + [Test] + public void TestConstruct_FromApp() + { + Assert.AreEqual( + InUseStateEnum.Reserved, + new StateRequestedInfo(() => new DateTime(2017, 09, 20, 12, 1, 0), new DateTime(2017, 09, 20, 12, 0, 0), "a@b", null).Value); + + Assert.AreEqual( + "a@b", + new StateRequestedInfo(() => new DateTime(2017, 09, 20, 12, 1, 0), new DateTime(2017, 09, 20, 12, 0, 0), "a@b", null).MailAddress); + + Assert.IsNull( + new StateRequestedInfo(() => new DateTime(2017, 09, 20, 12, 1, 0), new DateTime(2017, 09, 20, 12, 0, 0), "a@b", null).Code); + + Assert.AreEqual( + new DateTime(2017, 09, 20, 12, 0, 0), + new StateRequestedInfo(() => new DateTime(2017, 09, 20, 12, 1, 0), new DateTime(2017, 09, 20, 12, 0, 0), "a@b", null).From); + + Assert.IsTrue( + new StateRequestedInfo(() => new DateTime(2017, 09, 20, 12, 1, 0), new DateTime(2017, 09, 20, 12, 0, 0), "a@b", null).GetIsStillReserved(out TimeSpan? l_oRemainigTime)); + + Assert.AreEqual( + 14, + l_oRemainigTime.Value.Minutes); + } + + [Test] + public void TestConstruct_FromWebserver() + { + Assert.AreEqual( + InUseStateEnum.Reserved, + new StateRequestedInfo(() => new DateTime(2017,09, 20), new DateTime(2017, 09, 19), "a@b", "372").Value); + + Assert.AreEqual( + "a@b", + new StateRequestedInfo(() => new DateTime(2017, 09, 20), new DateTime(2017, 09, 19), "a@b", "372").MailAddress); + + Assert.AreEqual( + "372", + new StateRequestedInfo(() => new DateTime(2017, 09, 20), new DateTime(2017, 09, 19), "a@b", "372").Code); + + Assert.AreEqual( + new DateTime(2017,09, 19), + new StateRequestedInfo(() => new DateTime(2017, 09, 20), new DateTime(2017, 09, 19), "a@b", "372").From); + + Assert.IsTrue( + new StateRequestedInfo(() => new DateTime(2017, 09, 20, 12, 12, 0), new DateTime(2017, 09, 20, 12, 0, 0), "a@b", "372").GetIsStillReserved(out TimeSpan? l_oRemainigTime)); + + Assert.AreEqual( + 3, + l_oRemainigTime.Value.Minutes); + } + + [Test] + public void TestTryUpdateOnTimeElapsed() + { + var l_oReservedAt = new DateTime(2017, 09, 20, 22, 01, 00); + var l_oDateTimeMock = new DateTimeMocker( new List { + l_oReservedAt.Add(new TimeSpan(0, 4, 0)), // Reserved for 4 mns + l_oReservedAt.Add(new TimeSpan(0, 16, 0)) // Time elapsed since booking 16 mns + }); + + var l_oReservedInfo = new StateRequestedInfo(l_oDateTimeMock.GetDateTime, l_oReservedAt, "a@b", null); + Assert.AreEqual("a@b", l_oReservedInfo.MailAddress); + Assert.AreEqual(new DateTime(2017, 09, 20, 22, 01, 00), l_oReservedInfo.From, "a@b"); + + // Invoke first update (after simulated 4mns) + Assert.IsTrue(l_oReservedInfo.GetIsStillReserved(out TimeSpan? l_oRemainigTime)); + Assert.AreEqual(11, l_oRemainigTime.Value.Minutes); + Assert.AreEqual(new DateTime(2017, 09, 20, 22, 01, 00), l_oReservedInfo.From, "a@b"); + + // Invoke second update (after simulated 16 mns) + Assert.IsFalse(l_oReservedInfo.GetIsStillReserved(out l_oRemainigTime)); + Assert.IsNull(l_oRemainigTime); + Assert.AreEqual(new DateTime(2017, 09, 20, 22, 01, 00), l_oReservedInfo.From, "a@b"); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/State/TestStateRequestedInfoSerializeJSON.cs b/TestTINKLib/Fixtures/ObjectTests/State/TestStateRequestedInfoSerializeJSON.cs new file mode 100644 index 0000000..311c95f --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/State/TestStateRequestedInfoSerializeJSON.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using TINK.Model.State; + + +namespace TestTINKLib.Fixtures.State +{ + + [TestFixture] + public class TestStateReInquestedfoSerializeJSON + { + [Test] + public void TestSerializeJSON() + { + // Create object to test. + var l_oReservedInfo = new StateRequestedInfo( + () => new DateTime(2017, 09, 20), + new DateTime(2017, 09, 19), + "a@b", + "372"); + + // Serialize object + // Serialize object and verify. + var l_oDetected = JsonConvert.SerializeObject(l_oReservedInfo); + // Verify xml + const string EXPECTED = @"{""From"":""2017 - 09 - 19T00: 00:00"",""MailAddress"":""a @b"",""Code"":""372""}"; + Assert.AreEqual( + TestHelper.PrepareXmlForStringCompare(EXPECTED), + TestHelper.PrepareXmlForStringCompare(l_oDetected)); + + // Deserialize object. + var l_oInfoTarge = JsonConvert.DeserializeObject(l_oDetected); + + // Verify state. + Assert.AreEqual(InUseStateEnum.Reserved, l_oInfoTarge.Value); + Assert.AreEqual("a@b", l_oInfoTarge.MailAddress); + Assert.AreEqual("372", l_oInfoTarge.Code); + Assert.AreEqual(new DateTime(2017, 9, 19, 0, 0, 0), l_oInfoTarge.From); + } + + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Station/TestNullStation.cs b/TestTINKLib/Fixtures/ObjectTests/Station/TestNullStation.cs new file mode 100644 index 0000000..a68845b --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Station/TestNullStation.cs @@ -0,0 +1,26 @@ +using NUnit.Framework; +using System.Linq; +using TINK.Model.Station; + +namespace TestTINKLib.Fixtures.ObjectTests.Station +{ + [TestFixture] + public class TestNullStation + { + [Test] + public void TestConstruct() + { + var l_oNull = new NullStation(); + + // Was -1 before swiching type of id from int to string when switching from COPRI version v4.0 to v4.1 + Assert.That( + l_oNull.Id, + Is.Null); + + Assert.AreEqual(0, l_oNull.Group.ToList().Count); + Assert.AreEqual(string.Empty, l_oNull.StationName); + Assert.IsNaN(l_oNull.Position.Latitude); + Assert.IsNaN(l_oNull.Position.Longitude); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/Station/TestStation.cs b/TestTINKLib/Fixtures/ObjectTests/Station/TestStation.cs new file mode 100644 index 0000000..aee6f6f --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/Station/TestStation.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using TINK.Model.Station; + + +namespace TestTINKLib.Fixtures.Station +{ + + [TestFixture] + public class TestStation + { + [Test] + public void TestConstruct() + { + var l_oStation = new TINK.Model.Station.Station("7", new HashSet(new List { "TINK" }).ToList(), new Position(1,2), "Hallo"); + Assert.AreEqual("7", l_oStation.Id); + Assert.AreEqual("TINK", string.Join(",", l_oStation.Group)); + Assert.AreEqual(1, l_oStation.Position.Latitude); + Assert.AreEqual(2, l_oStation.Position.Longitude); + Assert.AreEqual("Hallo", l_oStation.StationName); + } + + [Test] + public void TestConstruct_InvalidStationName() + { + var l_oStation = new TINK.Model.Station.Station("7", new HashSet(new List { "TINK" }).ToList(), new Position(1, 2), null); + Assert.AreEqual("7", l_oStation.Id); + Assert.AreEqual("TINK", string.Join(",", l_oStation.Group)); + Assert.AreEqual(1, l_oStation.Position.Latitude); + Assert.AreEqual(2, l_oStation.Position.Longitude); + Assert.AreEqual("", l_oStation.StationName); + } + + [Test] + public void TestConstruct_InvalidStationGroup() + { + Assert.Throws(() => new TINK.Model.Station.Station("7", null, new Position(1, 2), "Hallo")); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/TestFilterCollection.cs b/TestTINKLib/Fixtures/ObjectTests/TestFilterCollection.cs new file mode 100644 index 0000000..cdeda49 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/TestFilterCollection.cs @@ -0,0 +1,26 @@ +using NUnit.Framework; +using System.Collections.Generic; +using TINK.Model; +using TINK.ViewModel.Map; + +namespace TestTINKLib.Fixtures.ObjectTests +{ + [TestFixture] + public class TestFilterCollection + { + [Test] + public void TestGetGroup() + { + var l_oFilterColl = new GroupFilterMapPage( new Dictionary + { + { "TINK", FilterState.On }, + { "Konrad", FilterState.On } + }); + + var l_oFilterGroup = l_oFilterColl.GetGroup(); + Assert.AreEqual(2, l_oFilterGroup.Count); + Assert.AreEqual("TINK", l_oFilterGroup[0]); + Assert.AreEqual("Konrad", l_oFilterGroup[1]); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/TestFilterCollectionStore.cs b/TestTINKLib/Fixtures/ObjectTests/TestFilterCollectionStore.cs new file mode 100644 index 0000000..1661106 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/TestFilterCollectionStore.cs @@ -0,0 +1,124 @@ +using NUnit.Framework; +using System.Collections.Generic; +using TINK.Model; +using TINK.Model.Settings; + +namespace TestTINKLib.Fixtures.ObjectTests +{ + [TestFixture] + public class TestFilterCollectionStore + { + [Test] + public void TestSerialize_Null() + { + var l_oSettings = new Dictionary().SetGroupFilterSettings(null); + var l_oFilterColl = JsonSettingsDictionary.GetGoupFilterSettings(l_oSettings); + + Assert.IsNull(l_oFilterColl); + } + + [Test] + public void TestSerialize_Invalid() + { + var l_oSettings = new Dictionary().SetGroupFilterSettings(new Dictionary()); + var l_oFilterColl = JsonSettingsDictionary.GetGoupFilterSettings(l_oSettings); + + Assert.IsNull(l_oFilterColl); + } + + [Test] + public void TestSerializeLegacy() + { + var l_oSettings = new Dictionary().SetGroupFilterSettings( + new Dictionary + { + { "TINK.Copri", FilterState.On }, + { "TINK.SMS", FilterState.Off}, + { "Konrad", FilterState.On } + }); + + var l_oFilterColl = JsonSettingsDictionary.GetGoupFilterSettings(l_oSettings); + + Assert.AreEqual(2, l_oFilterColl.Count); + Assert.AreEqual(FilterState.On, l_oFilterColl["TINK"]); + Assert.AreEqual(FilterState.On, l_oFilterColl["Konrad"]); + + l_oSettings = new Dictionary().SetGroupFilterSettings( + new Dictionary + { + { "TINK.Copri", FilterState.Off }, + { "TINK.SMS", FilterState.On}, + { "Konrad", FilterState.On } + }); + + l_oFilterColl = JsonSettingsDictionary.GetGoupFilterSettings(l_oSettings); + + Assert.AreEqual(2, l_oFilterColl.Count); + Assert.AreEqual(FilterState.Off, l_oFilterColl["TINK"]); + Assert.AreEqual(FilterState.On, l_oFilterColl["Konrad"]); + } + + [Test] + public void TestSerializeInvalid() + { + var l_oSettings = new Dictionary() + .SetGroupFilterSettings( + new Dictionary + { + { "TINK.SMS", FilterState.Off}, + { "Konrad", FilterState.On } + }); + + var l_oFilterColl = JsonSettingsDictionary.GetGoupFilterSettings(l_oSettings); + + Assert.AreEqual(2, l_oFilterColl.Count); + Assert.AreEqual(FilterState.Off, l_oFilterColl["TINK"]); + Assert.AreEqual(FilterState.On, l_oFilterColl["Konrad"]); + } + + [Test] + public void TestSerializeRoundtrip() + { + var l_oSettings = new Dictionary() + .SetGroupFilterSettings( + new Dictionary + { + { "TINK", FilterState.On }, + { "Konrad", FilterState.On } + }); + + var l_oFilterColl = JsonSettingsDictionary.GetGoupFilterSettings(l_oSettings); + + Assert.AreEqual(2, l_oFilterColl.Count); + Assert.AreEqual(FilterState.On, l_oFilterColl["TINK"]); + Assert.AreEqual(FilterState.On, l_oFilterColl["Konrad"]); + + l_oSettings = new Dictionary().SetGroupFilterSettings( + new Dictionary + { + { "TINK", FilterState.Off }, + { "Konrad", FilterState.On } + }); + + l_oFilterColl = JsonSettingsDictionary.GetGoupFilterSettings(l_oSettings); + + Assert.AreEqual(2, l_oFilterColl.Count); + Assert.AreEqual(FilterState.Off, l_oFilterColl["TINK"]); + Assert.AreEqual(FilterState.On, l_oFilterColl["Konrad"]); + } + + [Test] + public void Test_ToString_Emtpy() + { + Assert.AreEqual("{}", FilterCollectionStore.ToString(new Dictionary())); + } + + [Test] + public void Test_ToString() + { + Assert.AreEqual( + "{(Test1= Off), (Test2= On)}", + FilterCollectionStore.ToString(new Dictionary { { "Test1", FilterState.Off}, {"Test2", FilterState.On}})); + } +} +} diff --git a/TestTINKLib/Fixtures/ObjectTests/TestJsonSettingsDictionary.cs b/TestTINKLib/Fixtures/ObjectTests/TestJsonSettingsDictionary.cs new file mode 100644 index 0000000..aa8272b --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/TestJsonSettingsDictionary.cs @@ -0,0 +1,126 @@ +using NUnit.Framework; +using Serilog.Events; +using System; +using System.Collections.Generic; +using TINK.Model.Settings; +using TINK.Settings; + +namespace TestTINKLib.Fixtures.ObjectTests +{ + [TestFixture] + public class TestJsonSettingsDictionary + { + /// Verifies that empty log fiel leads to expected default value and doesn not throw exceptions. + [Test] + public void TestGetCopriHostUri_NoFile() + { + Assert.IsNull(JsonSettingsDictionary.GetCopriHostUri(new Dictionary())); + } + + /// Verifies that empty log file leads to expected default value and doesn not throw exceptions. + [Test] + public void TestGetPolling() + { + // Serialize parameters. + var l_oDict = new Dictionary() + .SetPollingParameters(new PollingParameters(new TimeSpan(0, 0, 0, 15, 0), false)); + + // Deserialize parameters. + Assert.AreEqual( + new PollingParameters(new TimeSpan(0, 0, 0, 15, 0), false), + l_oDict.GetPollingParameters()); + } + + /// Verifies that empty log fiel leads to expected default value and doesn not throw exceptions. + [Test] + public void TestGetPolling_NoFile() + { + Assert.IsNull(JsonSettingsDictionary.GetPollingParameters(new Dictionary())); + } + + [Test] + public void TestGetGetCopriHostUri() + { + var l_oDict = new Dictionary() + .SetCopriHostUri("http://1.2.3.4"); + Assert.AreEqual( + new Uri("http://1.2.3.4"), + JsonSettingsDictionary.GetCopriHostUri(l_oDict)); + } + + /// Verifies that empty log fiel leads to expected default value and doesn not throw exceptions. + [Test] + public void TestGetLoggingLevel() + { + var l_oDictionary = new Dictionary() + .SetMinimumLoggingLevel(0); // Verbose = 0 + Assert.AreEqual( + LogEventLevel.Verbose, + JsonSettingsDictionary.GetMinimumLoggingLevel(l_oDictionary)); // LogEventLevel.Error = 4 + } + + /// Verifies that empty log fiel leads to expected default value and doesn not throw exceptions. + [Test] + public void TestGetLoggingLevel_NoFile() + { + Assert.IsNull(JsonSettingsDictionary.GetMinimumLoggingLevel(new Dictionary())); + } + + [Test] + public void TestGetAppVersion_FirstInstall() + { + var l_oDict = new Dictionary (); + Assert.IsNull(JsonSettingsDictionary.GetAppVersion(l_oDict)); + } + + [Test] + public void TestGetAppVersion_LegacyTo115() + { + var l_oDict = new Dictionary { { "AppVersion", "7.2.3.9" } }; + Assert.AreEqual(new Version(7,2,3,9), JsonSettingsDictionary.GetAppVersion(l_oDict)); + + l_oDict = new Dictionary { { "AppVersion", "7.2.3" } }; + Assert.AreEqual(new Version(7, 2, 3), JsonSettingsDictionary.GetAppVersion(l_oDict)); + } + + [Test] + public void TestGetAppVersion_Json() + { + var l_oDict = new Dictionary { { "AppVersion", "\"3.1.2.117\"" } }; + Assert.AreEqual(new Version(3, 1, 2, 117), JsonSettingsDictionary.GetAppVersion(l_oDict)); + } + + [Test] + public void TestSetAppVersion_Json() + { + var l_oDict = new Dictionary() + .SetAppVersion(new Version(47, 12, 3)); + Assert.AreEqual(new Version(47, 12, 3), JsonSettingsDictionary.GetAppVersion(l_oDict)); + } + + [Test] + public void TestGetShowWhatsNew_FirstInstall() + { + var l_oDict = new Dictionary(); + Assert.IsNull(JsonSettingsDictionary.GetWhatsNew(l_oDict)); + } + + [Test] + public void TestGetShowWhatsNew_Json() + { + var l_oDict = new Dictionary { { "ShowWhatsNew", "\"3.1.2.117\"" } }; + Assert.AreEqual(new Version(3, 1, 2, 117), JsonSettingsDictionary.GetWhatsNew(l_oDict)); + + l_oDict = new Dictionary { { "ShowWhatsNew", "\"3.1.2\"" } }; + Assert.AreEqual(new Version(3, 1, 2), JsonSettingsDictionary.GetWhatsNew(l_oDict)); + } + + [Test] + public void TestSetShowWhats_Json() + { + var l_oDict = new Dictionary() + .SetWhatsNew(new Version(47, 12, 3)); + Assert.AreEqual(new Version(47, 12, 3), JsonSettingsDictionary.GetWhatsNew(l_oDict)); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/TestPollingTaskManager.cs b/TestTINKLib/Fixtures/ObjectTests/TestPollingTaskManager.cs new file mode 100644 index 0000000..3935d93 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/TestPollingTaskManager.cs @@ -0,0 +1,46 @@ +using NUnit.Framework; +using System.Threading.Tasks; +using TINK.ViewModel; +using TINK.Settings; + +namespace UITest.Fixtures.ObjectTests +{ + [TestFixture] + public class TestPollingTaskManager + { + [Test] + public async Task TestStopUpdatePeriodiallyRepeated() + { + var l_oManger = new PollingUpdateTaskManager(() => "Test", () => Task.Delay(1000)); + + await l_oManger.StartUpdateAyncPeridically(new PollingParameters(new System.TimeSpan(0, 0, 2), true)); + + l_oManger.StopUpdatePeridically().Wait(); + + // Should not lead to dead lock. + l_oManger.StopUpdatePeridically().Wait(); + } + + [Test] + public async Task TestStartUpdatePeriodiallyRepeated() + { + var l_oManger = new PollingUpdateTaskManager(() => "Test", () => Task.Delay(1000)); + + await l_oManger.StartUpdateAyncPeridically(new PollingParameters(new System.TimeSpan(0, 0, 2), true)); + + // Should not lead to dead lock. + await l_oManger.StartUpdateAyncPeridically(new PollingParameters(new System.TimeSpan(0, 0, 2), true)); + + l_oManger.StopUpdatePeridically().Wait(); + } + + [Test] + public async Task TestStopUpdatePeriodiallyNoStart() + { + var l_oManger = new PollingUpdateTaskManager(() => "Test", () => Task.Delay(1000)); + + // Should not lead to dead lock. + await l_oManger.StartUpdateAyncPeridically(new PollingParameters(new System.TimeSpan(0, 0, 2), true)); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/TestPollingUpdateTask.cs b/TestTINKLib/Fixtures/ObjectTests/TestPollingUpdateTask.cs new file mode 100644 index 0000000..7aa5f4b --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/TestPollingUpdateTask.cs @@ -0,0 +1,21 @@ +using NUnit.Framework; +using System.Threading.Tasks; +using TINK.ViewModel; + +namespace TestTINKLib.Fixtures.ObjectTests +{ + [TestFixture] + public class TestPollingUpdateTask + { + [Test] + public void TestTerminateRepeated() + { + var l_oTaks = new PollingUpdateTask(() => "Test", async () => await Task.Delay(1000), new System.TimeSpan(0, 0, 2)); + + l_oTaks.Terminate().Wait(); + + // Verify that calling terminate twice does not lead to hang of call. + l_oTaks.Terminate().Wait(); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/TestWhatsNew.cs b/TestTINKLib/Fixtures/ObjectTests/TestWhatsNew.cs new file mode 100644 index 0000000..e08459e --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/TestWhatsNew.cs @@ -0,0 +1,76 @@ +using NUnit.Framework; +using System; +using TINK.Model; + +namespace TestTINKLib.Fixtures.ObjectTests +{ + [TestFixture] + public class TestWhatsNew + { + [Test] + public void TestIsShowRequired_CleanInstall() + { + Assert.IsFalse(new WhatsNew( + new Version(2, 4), /* Current version */ + null, // last version + null /* Whats new was never shown */ ).IsShowRequired); + } + + [Test] + public void TestIsShowRequired_Update_VersionWhichSupporsWhatsNew() + { + Assert.IsTrue( + new WhatsNew( + new Version(2, 5), /* Current version */ + new Version(2, 3), /* last version */ + new Version(2, 1) /* Version when whats new was shown */ ).IsShowRequired, + "If whats new was last shonw for version 2.1 and current version is 2.5 info must be shown"); + } + + + [Test] + public void TestIsShowRequired_Update_VersionWithoutWhatsNew() + { + Assert.IsTrue( + new WhatsNew( + new Version(2, 5), /* Current version */ + new Version(2, 3), /* last version */ + null /* Whats new was never shown */ ).IsShowRequired, + "If whats new was never shown but last version was 2.3 (not clean install) info must be shown"); + } + + [Test] + public void TestIsShowRequired_False() + { + Assert.IsFalse( + new WhatsNew(new Version(2, 5), new Version(2, 5), new Version(2, 5)).IsShowRequired, + "Whats new must never be presented twice."); + } + + [Test] + public void TestIsShowRequired_False_Errors() + { + Assert.IsFalse(new WhatsNew(null, new Version(2,6), new Version(2, 5)).IsShowRequired); + Assert.IsFalse(new WhatsNew(new Version(2, 6), null, new Version(2, 5)).IsShowRequired); + Assert.IsFalse(new WhatsNew(null, null, new Version(2, 5)).IsShowRequired); + } + + [Test] + public void TestWhatsNewText_MajorUpdate() + { + Assert.AreEqual( + "

3.0.0.115
Benutzeroberfläche verbessert.\r\n\r\n" + + "

3.0.120
Verbesserung: Keine Fehler mehr beim schnellen Tippen.\r\nOfflineanzeige Stationen/ Räderinfo.\r\n\r\n

", + new WhatsNew(new Version(3, 0, 120), new Version(3, 0, 0, 114), new Version(3, 0, 0, 114)).WhatsNewText, + "Current version is 30.0.120, last whats new was shown in version 3.0.0.114: Two changes to be notified about."); + } + + [Test] + public void TestWhatsNewText_MinorUpdate() + { + Assert.AreEqual( + "

3.0.120
Verbesserung: Keine Fehler mehr beim schnellen Tippen.\r\nOfflineanzeige Stationen/ Räderinfo.\r\n\r\n

", + new WhatsNew(new Version(3, 0, 120), new Version(3, 0, 0, 115), new Version(3, 0, 0, 115)).WhatsNewText); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/User/Account/TestAccount.cs b/TestTINKLib/Fixtures/ObjectTests/User/Account/TestAccount.cs new file mode 100644 index 0000000..3fbbf61 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/User/Account/TestAccount.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; + +namespace UITest.Fixtures.ObjectTests.User.Account +{ + [TestFixture] + public class TestAccount + { + [Test] + public void TestConstruct() + { + var l_oAccount = new TINK.Model.User.Account.Account("a@b", "112", "3330", new List { "Honkey", "Tonkey" }, TINK.Model.User.Account.Permissions.None); + + Assert.AreEqual("a@b", l_oAccount.Mail); + Assert.AreEqual("112", l_oAccount.Pwd); + Assert.AreEqual("3330", l_oAccount.SessionCookie); + Assert.AreEqual("Honkey,Tonkey", String.Join(",", l_oAccount.Group)); + Assert.AreEqual(TINK.Model.User.Account.Permissions.None, l_oAccount.DebugLevel); + } + + [Test] + public void TestConstruct_Copy() + { + var l_oAccount = new TINK.Model.User.Account.Account(new TINK.Model.User.Account.Account("a@b", "112", "3330", new List { "Honkey", "Tonkey" },TINK.Model.User.Account.Permissions.None)); + + Assert.AreEqual("a@b", l_oAccount.Mail); + Assert.AreEqual("112", l_oAccount.Pwd); + Assert.AreEqual("3330", l_oAccount.SessionCookie); + Assert.AreEqual("Honkey,Tonkey", String.Join(",", l_oAccount.Group)); + Assert.AreEqual(TINK.Model.User.Account.Permissions.None, l_oAccount.DebugLevel); + } + + [Test] + public void TestConstruct_InvalidGroup() + { + Assert.Throws(() => new TINK.Model.User.Account.Account("a@b", "112", "3330", null, TINK.Model.User.Account.Permissions.None)); + Assert.Throws(() => new TINK.Model.User.Account.Account(null)); + + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/User/Account/TestAccountExtensions.cs b/TestTINKLib/Fixtures/ObjectTests/User/Account/TestAccountExtensions.cs new file mode 100644 index 0000000..8e6448c --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/User/Account/TestAccountExtensions.cs @@ -0,0 +1,50 @@ +using NUnit.Framework; +using Rhino.Mocks; +using System.Collections.Generic; +using System.Linq; +using TINK.Model.User.Account; + +namespace TestTINKLib.Fixtures.ObjectTests.User.Account +{ + [TestFixture] + public class TestAccountExtensions + { + /// If no user is logged in filter is not applied. + [Test] + public void TestDoFilter_NoUserLoggedIn() + { + var l_oAccount = MockRepository.GenerateMock(); + + l_oAccount.Stub((x) => x.Mail).Return("a@b"); + l_oAccount.Stub((x) => x.SessionCookie).Return(""); // User is not logged in + l_oAccount.Stub((x) => x.Group).Return(new List { "TINK", "FutureType" }); + + var l_oSource = new List { "TINK", "Konrad", "FutureType" }; + + var l_oResult = l_oAccount.DoFilter(l_oSource); + + Assert.AreEqual(3, l_oResult.ToList().Count); + Assert.AreEqual("TINK", l_oResult.ToList()[0]); + Assert.AreEqual("Konrad", l_oResult.ToList()[1]); + Assert.AreEqual("FutureType", l_oResult.ToList()[2]); + } + /// + [Test] + public void TestDoFilter() + { + var l_oAccount = MockRepository.GenerateMock(); + + l_oAccount.Stub((x) => x.Mail).Return("a@b"); + l_oAccount.Stub((x) => x.SessionCookie).Return("123"); + l_oAccount.Stub((x) => x.Group).Return(new List { "TINK", "FutureType" }); + + var l_oSource = new List { "TINK", "Konrad", "FutureType" }; + + var l_oResult = l_oAccount.DoFilter(l_oSource); + + Assert.AreEqual(2, l_oResult.ToList().Count); + Assert.AreEqual("TINK", l_oResult.ToList()[0]); + Assert.AreEqual("FutureType", l_oResult.ToList()[1]); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/User/Account/TestStore.cs b/TestTINKLib/Fixtures/ObjectTests/User/Account/TestStore.cs new file mode 100644 index 0000000..125ef1e --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/User/Account/TestStore.cs @@ -0,0 +1,24 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using TINK.Model.User.Account; + +namespace UITest.Fixtures.ObjectTests.User.Account +{ + [TestFixture] + public class TestStore + { + [Test, Explicit("Did never ran, file load exception.")] + public void TestLoadSaveRoundtrip() + { + var l_oStore = new Store("4711"); + l_oStore.Save(new TINK.Model.User.Account.Account("a@b", "112", "3330", new List { "Honkey", "Tonkey" }, 17)); + var l_oAccount = l_oStore.Load(); + Assert.AreEqual("a@b", l_oAccount.Mail); + Assert.AreEqual("112", l_oAccount.Pwd); + Assert.AreEqual("3330", l_oAccount.SessionCookie); + Assert.AreEqual("Honkey,Tonkey", String.Join(",", l_oAccount.Group)); + Assert.AreEqual(17, l_oAccount.DebugLevel); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/User/Account/TestValidator.cs b/TestTINKLib/Fixtures/ObjectTests/User/Account/TestValidator.cs new file mode 100644 index 0000000..33717e7 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/User/Account/TestValidator.cs @@ -0,0 +1,30 @@ +using NUnit.Framework; +using TINK.Model.User.Account; + + +namespace TestTINKLib +{ + + [TestFixture] + public class TestValidator + { + [Test] + public void TestValidateMailAndPasswordDelegate() + { + Assert.AreEqual(Elements.None, Validator.ValidateMailAndPasswordDelegate(null, null).ValidElement); + + Assert.AreEqual(Elements.None, Validator.ValidateMailAndPasswordDelegate("", null).ValidElement); + + Assert.AreEqual(Elements.None, Validator.ValidateMailAndPasswordDelegate("a", null).ValidElement); + + Assert.AreEqual(Elements.None, Validator.ValidateMailAndPasswordDelegate("a@", null).ValidElement); + + + Assert.AreEqual(Elements.Mail, Validator.ValidateMailAndPasswordDelegate("a@c", "").ValidElement); + + Assert.AreEqual(Elements.Mail, Validator.ValidateMailAndPasswordDelegate("a@c", "123").ValidElement); + + Assert.AreEqual(Elements.Mail | Elements.Password, Validator.ValidateMailAndPasswordDelegate("a@c", "123456789" /* password */).ValidElement); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/User/TestUser.cs b/TestTINKLib/Fixtures/ObjectTests/User/TestUser.cs new file mode 100644 index 0000000..b5a57e1 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/User/TestUser.cs @@ -0,0 +1,97 @@ +using NUnit.Framework; +using TINK.Model.User; +using TINK.Model.Connector; +using TestTINKLib.Model.User.Account; + +using TINK.Model.User.Account; +using System.Collections.Generic; +using TINK.Repository; + +using static TINK.Repository.CopriCallsMemory; + +namespace TestTINKLib +{ + + [TestFixture] + public class TestUser + { + [Test] + public void TestConstruct_NotLoggedIn_NoUsername() + { + var l_oStoreMock = new StoreMock(); // Account without user name, password and cookie + + var l_oUser = new User( + l_oStoreMock, + l_oStoreMock.Load().Result, + "HwId1000000000000"); + + Assert.IsFalse(l_oUser.IsLoggedIn); + Assert.IsNull(l_oUser.Mail); + Assert.IsNull(l_oUser.Password); + Assert.IsNull(l_oUser.SessionCookie); + } + + [Test] + public void TestConstruct_NotLoggedIn_NoCookie() + { + var l_oStoreMock = new StoreMock(new Account("John", "123", null, new List())); // Account without session cookie. + + var l_oUser = new User( + l_oStoreMock, + l_oStoreMock.Load().Result, + "123456789"); + + Assert.IsFalse(l_oUser.IsLoggedIn); + Assert.AreEqual("John", l_oUser.Mail); + Assert.AreEqual("123", l_oUser.Password); + Assert.IsNull(l_oUser.SessionCookie); + } + + [Test] + public void TestConstruct_LoggedIn() + { + var l_oStoreMock = new StoreMock(new Account("John", "123", "9512", new List { "TINK" })); + + var l_oUser = new User( + l_oStoreMock, + l_oStoreMock.Load().Result, + "123456789"); + + Assert.IsTrue(l_oUser.IsLoggedIn, "If store does not hold cookie user is considered to not be logged in"); + + Assert.AreEqual("John", l_oUser.Mail); + Assert.AreEqual("123", l_oUser.Password); + Assert.AreEqual("9512", l_oUser.SessionCookie); + } + + /// Test logging in. + [Test] + public void TestSetCredentials() + { + var l_oConnector = new ConnectorCache( + string.Empty, + string.Empty, + new CopriCallsMemory(SampleSets.Set2, 1)); + + var l_oStoreMock = new StoreMock(); // Account without user name, password and cookie + + var l_oUser = new User( + l_oStoreMock, + l_oStoreMock.Load().Result, + "HwId1000000000000"); + + Assert.IsFalse(l_oUser.IsLoggedIn); + Assert.IsNull(l_oUser.Mail); + + IAccount l_oAccount = l_oConnector.Command.DoLogin( + LoginSessionCopriInfo.JavaministerHardwareNr1.Mail, + LoginSessionCopriInfo.JavaministerHardwareNr1.Pwd, + l_oUser.DeviceId).Result; + + l_oUser.Login(l_oAccount); + Assert.IsTrue(l_oUser.IsLoggedIn); + + Assert.AreEqual(LoginSessionCopriInfo.JavaministerHardwareNr1.Mail, l_oUser.Mail); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Account/TestAccountPageViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Account/TestAccountPageViewModel.cs new file mode 100644 index 0000000..8b0e1aa --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Account/TestAccountPageViewModel.cs @@ -0,0 +1,237 @@ +using NUnit.Framework; +using Rhino.Mocks; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using TestTINKLib.Mocks.Connector; +using TestTINKLib.Mocks.Device; +using TestTINKLib.Mocks.Services; +using TestTINKLib.Model.User.Account; +using TINK.Model; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.View; +using TINK.Model.Services.CopriApi; +using TINK.Repository; + + +using static TINK.Repository.CopriCallsMemory; +using TINK.ViewModel.Map; +using TINK.ViewModel.Settings; +using TINK.ViewModel.Account; +using TINK.Services; +using TINK.Model.Services.Geolocation; +using NSubstitute; + +namespace TestTINKLib.Fixtures.ObjectTests.Account +{ + public class TestAccountPageViewModel + { + [Test] + public void TestConstruct_NotLoggedIn() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var viewService = MockRepository.GenerateStub(); + + var settingsPageViewModel = new AccountPageViewModel( + tinkApp, + (uri) => { }, + viewService); + + + Assert.AreEqual("No user logged in.", settingsPageViewModel.LoggedInInfo); + Assert.IsFalse(settingsPageViewModel.IsBookingStateInfoVisible, "No user logged in."); + Assert.AreEqual(string.Empty, settingsPageViewModel.BookingStateInfo); + } + + [Test] + public async Task TestConstruct() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "UnknownCookie", new List { "TINK" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var viewService = MockRepository.GenerateStub(); + + var settingsPageViewModel = new AccountPageViewModel( + tinkApp, + (uri) => { }, + viewService); + + await settingsPageViewModel.OnAppearing(); + + Assert.AreEqual("Logged in as a@b at TINK.", settingsPageViewModel.LoggedInInfo); + Assert.IsFalse(settingsPageViewModel.IsBookingStateInfoVisible, "A user is logged but no bikes requested/ booked."); + Assert.AreEqual(string.Empty, settingsPageViewModel.BookingStateInfo); + } + + [Test] + public async Task TestConstruct_TwoBikes() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "4da3044c8657a04ba60e2eaa753bc51a", new List { "TINK" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var viewService = MockRepository.GenerateStub(); + var settingsPageViewModel = new AccountPageViewModel( + tinkApp, + (uri) => { }, + viewService); + + await settingsPageViewModel.OnAppearing(); + + Assert.AreEqual("Logged in as a@b at TINK.", settingsPageViewModel.LoggedInInfo); + Assert.IsTrue(settingsPageViewModel.IsBookingStateInfoVisible, "A user is logged but no bikes requested/ booked."); + Assert.AreEqual("Aktuell 2 Fahrräder reserviert/ gebucht.", settingsPageViewModel.BookingStateInfo); + } + + [Test] + public async Task TestConstruct_TwoBikes_Offline() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "4da3044c8657a04ba60e2eaa753bc51a", new List { "TINK" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => false, // Offline + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var viewService = MockRepository.GenerateStub(); + var settingsPageViewModel = new AccountPageViewModel( + tinkApp, + (uri) => { }, + viewService); + + await settingsPageViewModel.OnAppearing(); + + Assert.AreEqual("Logged in as a@b at TINK.", settingsPageViewModel.LoggedInInfo); + Assert.IsTrue(settingsPageViewModel.IsBookingStateInfoVisible, "A user is logged but no bikes requested/ booked."); + Assert.AreEqual("Aktuell 2 Fahrräder reserviert/ gebucht. Verbindungsstatus: Offline.", settingsPageViewModel.BookingStateInfo); + } + + [Test] + public async Task TestConstruct_TwoBikes_WebConnectCommunicationError() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "4da3044c8657a04ba60e2eaa753bc51a", new List { "TINK" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new TINK.Model.Connector.Connector( + uri, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie, + mail, + server: new CopriProviderHttps( + uri, + TinkApp.MerchantId, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie: sessionCookie, + cacheServer: new CopriCallsCacheMemory(SampleSets.Set2, sessionCookie: sessionCookie), + httpsServer: new ExceptionServer((msg) => new WebConnectFailureException(msg, new Exception("Source expection."))))), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => false, // Offline + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var viewService = MockRepository.GenerateStub(); + var settingsPageViewModel = new AccountPageViewModel( + tinkApp, + (uri) => { }, + viewService); + + await settingsPageViewModel.OnAppearing(); + + Assert.AreEqual("Logged in as a@b at TINK.", settingsPageViewModel.LoggedInInfo); + Assert.IsTrue(settingsPageViewModel.IsBookingStateInfoVisible, "A user is logged but no bikes requested/ booked."); + Assert.AreEqual("Aktuell 2 Fahrräder reserviert/ gebucht. Verbindungsstatus: Offline.", settingsPageViewModel.BookingStateInfo); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedClosed.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedClosed.cs new file mode 100644 index 0000000..39f37e8 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedClosed.cs @@ -0,0 +1,1335 @@ +using NSubstitute; +using NUnit.Framework; +using System; +using System.Threading.Tasks; +using TINK.Model.Bike.BluetoothLock; +using TINK.Model.Bikes.Bike.BluetoothLock; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.Services.BluetoothLock; +using TINK.Services.BluetoothLock.Exception; +using TINK.Services.BluetoothLock.Tdo; +using TINK.Model.Services.Geolocation; +using TINK.Model.State; +using TINK.View; +using TINK.ViewModel; +using TINK.ViewModel.Bikes; +using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; +using TINK.Model.User; +using TINK.Repository.Request; +using TINK.Repository.Response; +using Newtonsoft.Json; +using TINK.Model.Device; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler +{ + [TestFixture] + public class TestBookedClosed + { + /// + /// Test construction of object. + /// + [Test] + public void Testctor() + { + var handler = new BookedClosed( + Substitute.For(), + () => true, // isConnectedDelegate + (isConnexted) => Substitute.For(), + Substitute.For(), + Substitute.For(), + () => Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + + // Verify prerequisites. + Assert.AreEqual("Return bike", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Return bike. + /// Comment: User deceide to abort returning bike + /// Final state: Same as initial state. + /// + [Test] + public void TestReturnCancel() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(false)); + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein"); + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Return bike. + /// Final state: Disposable closed + /// + [Test] + public void TestReturnOutOfReach() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].GetDeviceState().Returns(DeviceState.Disconnected); // Simulate bike out of reach. + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Gebe Rad zurück..."; + connector.Command.DoReturn(bike, Arg.Is(x => x == null)); + bikesViewModel.ActionText = "Disconnecting lock..."; + locks.DisconnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Return bike. + /// Final state: Disposable closed + /// + [Test] + public void TestReturnLockInReach() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown. + + geolocation.GetAsync(Arg.Any()).Returns(Task.FromResult( + new Xamarin.Essentials.Location(7, 9) + )); + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Abfrage Standort..."; + geolocation.GetAsync(Arg.Any()); + bikesViewModel.ActionText = "Gebe Rad zurück..."; + connector.Command.DoReturn(bike, Arg.Is(x => x != null)); + bikesViewModel.ActionText = "Disconnecting lock..."; + locks.DisconnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Return bike. + /// Final state: Disposable closed + /// + [Test] + public void TestReturnLockInReachNoGeolocation() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown. + + geolocation.GetAsync(Arg.Any()).Returns(x => throw new Exception()); + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Abfrage Standort..."; + geolocation.GetAsync(Arg.Any()); + bikesViewModel.ActionText = "Gebe Rad zurück..."; + connector.Command.DoReturn(bike, Arg.Is(x => x == null)); + bikesViewModel.ActionText = "Disconnecting lock..."; + locks.DisconnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Return bike. + /// Final state: Same as initial state. + /// + [Test] + public void TestReturnReturnFailsWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown. + + connector.Command.DoReturn(bike, Arg.Any()).Returns(x => throw new WebConnectFailureException("Context info", new Exception("hoppla"))); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Gebe Rad zurück..."; + connector.Command.DoReturn(bike, Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Verbingungsfehler beim Zurückgeben des Rads!", "Internet muss erreichbar sein zum Zurückgeben des Rads.\r\nContext info\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Return bike. + /// Final state: Same as initial state. + /// + [Test] + public void TestReturnReturnFailsNotAtStationException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown. + + NotAtStationException.IsNotAtStation("Failure 2178: bike 1545 out of GEO fencing. 15986 meter distance to next station 42. OK: bike 1545 locked confirmed", out NotAtStationException notAtStationException); + + connector.Command.DoReturn(bike, Arg.Any()).Returns(x => + throw notAtStationException); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Gebe Rad zurück..."; + connector.Command.DoReturn(bike, Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Error returning bike!", "Returning bike outside of station is not possible. Distance to station 42 is 15986 m.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Return bike. + /// Final state: Same as initial state. + /// + [Test] + public void TestReturnReturnFailsNoGPSDataException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown. + + NoGPSDataException.IsNoGPSData("Failure 2245: No GPS data, state change forbidden.", out NoGPSDataException noGPSDataException); + + connector.Command.DoReturn(bike, Arg.Any()).Returns(x => + throw noGPSDataException); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Gebe Rad zurück..."; + connector.Command.DoReturn(bike, Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Error returning bike!", "Returning bike at an unknown location is not possible.\r\nBike can be returned if\r\n- location information is available when closing lock\r\n- bike is in reach and location information is available when pressing button \"Return bike\"", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Return bike. + /// Final state: Same as initial state. + /// + [Test] + public void TestReturnReturnFailsResponseException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown. + + connector.Command.DoReturn(bike, Arg.Any()).Returns(x => + throw new ReturnBikeException(JsonConvert.DeserializeObject(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Gebe Rad zurück..."; + connector.Command.DoReturn(bike, Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAdvancedAlert("Statusfehler beim Zurückgeben des Rads!", "Outer message.", "Some invalid data received!", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Return bike. + /// Final state: Same as initial state. + /// + [Test] + public void TestReturnReturnFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown. + + connector.Command.DoReturn(bike, Arg.Any()).Returns(x => throw new Exception("Exception message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Gebe Rad zurück..."; + connector.Command.DoReturn(bike, Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler beim Zurückgeben des Rads!", "Exception message.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Open lock + /// Final state: Booked opened. + /// + [Test] + public void TestOpen() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Open)); // Return lock state indicating success + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, null); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Open lock + /// Final state: Same as initial state. + /// + [Test] + public void TestOpenOpenFailsOutOfReachException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns>(x => throw new OutOfReachException()); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Closed); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Error while opening lock!", "Lock cannot be opened until bike is near.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Disconnected" after action + Assert.AreEqual(nameof(BookedDisconnected), subsequent.ButtonText); + Assert.IsFalse(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Open lock + /// Final state: Same as initial state. + /// + [Test] + public void TestOpenOpenFailsCouldntOpenBoldBlockedException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns>(x => throw new CouldntOpenBoldIsBlockedException()); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Closed); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Error while opening lock!", "Lock is blocked. Please ensure that no obstacle prevents lock from opening and try again.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Unknown" after action + Assert.AreEqual("Open lock & continue renting", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + + /// + /// Use case: Open lock + /// Final state: Same as initial state. + /// + [Test] + public void TestOpenOpenFailsCouldntOpenInconsistentStateExecptionClosed() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns>(x => throw new CouldntOpenInconsistentStateExecption(LockingState.Closed)); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Closed); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Error while opening lock!", "After try to open lock state closed is reported.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Open lock + /// Final state: Same as initial state. + /// + [Test] + public void TestOpenOpenFailsCouldntOpenInconsistentStateExecption() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns>(x => throw new CouldntOpenBoldWasBlockedException()); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Closed); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Lock can not be opened!", "Lock was blocked and might still be. Please ensure that no obstacle prevents lock from opening and try again.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Disconnected" after action + Assert.AreEqual("Open lock & continue renting", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Open lock + /// Final state: Same as initial state. + /// + [Test] + public void TestOpenOpenFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns>(x => throw new Exception("Exception message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Closed); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Error while opening lock!", "Exception message.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Disconnected" after action + Assert.AreEqual(nameof(BookedDisconnected), subsequent.ButtonText); + Assert.IsFalse(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Open lock + /// Final state: Booked opened. + /// + [Test] + public void TestOpenGetBatteryPercentageAsyncThrowsOutOfReachException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Open)); // Return lock state indicating success + + locks[0].GetBatteryPercentageAsync().Returns>(x => throw new OutOfReachException()); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Battery status can only be read when bike is nearby."; + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, null); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Open lock + /// Final state: Booked opened. + /// + [Test] + public void TestOpenGetBatteryPercentageAsyncThrowsExcepton() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Open)); // Return lock state indicating success + + locks[0].GetBatteryPercentageAsync().Returns>(x => throw new Exception()); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Battery status cannot be read."; + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, null); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Open lock + /// Final state: Booked open. + /// + [Test] + public void TestOpenUpdateLockingStateFailsWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns(LockitLockingState.Open); + + connector.Command.UpdateLockingStateAsync(bike, null).Returns(x => throw new WebConnectFailureException("Context info", new System.Exception("hoppla"))); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Open); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, null); + bikesViewModel.ActionText = "No web error on updating locking status."; + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Open lock + /// Final state: Booked open. + /// + [Test] + public void TestOpenUpdateLockingStateFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns(LockitLockingState.Open); + + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new Exception("Exception message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Open); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()); + bikesViewModel.ActionText = "Connection error on updating locking status."; + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Open lock + /// Final state: Booked open. + /// + [Test] + public void TestOpenUpdateLockingStateFailsResponseException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedClosed( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].OpenAsync() + .Returns(LockitLockingState.Open); + + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => + throw new ReturnBikeException(JsonConvert.DeserializeObject(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Open); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be closed + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()); + bikesViewModel.ActionText = "Status error on updating lock state."; + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedDisconnected.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedDisconnected.cs new file mode 100644 index 0000000..c6a499a --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedDisconnected.cs @@ -0,0 +1,462 @@ +using NSubstitute; +using NUnit.Framework; +using System; +using System.Threading.Tasks; +using TINK.Model.Bike.BluetoothLock; +using TINK.Model.Bikes.Bike.BluetoothLock; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.Services.BluetoothLock; +using TINK.Services.BluetoothLock.Exception; +using TINK.Services.BluetoothLock.Tdo; +using TINK.Model.Services.Geolocation; +using TINK.Model.State; +using TINK.View; +using TINK.ViewModel; +using TINK.ViewModel.Bikes; +using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; +using TINK.Model.User; +using TINK.Model.Device; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler +{ + [TestFixture] + public class TestBookedDisconnected + { + /// + /// Test construction of object. + /// + [Test] + public void Testctor() + { + var handler = new BookedDisconnected( + Substitute.For(), + () => true, // isConnectedDelegate + (isConnexted) => Substitute.For(), + Substitute.For(), + Substitute.For(), + () => Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + + // Verify prerequisites. + Assert.AreEqual("BookedDisconnected", handler.ButtonText); + Assert.IsFalse(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + [Test] + public void TestNotSupported() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify that nothing happened because request is not supported. + Assert.AreEqual("BookedDisconnected", subsequent.ButtonText); + Assert.IsFalse(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Booked open + /// + [Test] + public void TestSearch() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new BookedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + + locks.ConnectAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(new LockInfoTdo.Builder { State = LockitLockingState.Open }.Build())); + locks.TimeOut.Returns(timeOuts); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + connector.Command.CalculateAuthKeys(bike); + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Booked unknown + /// + [Test] + public void TestSearchCalculateAuthKeysFailsWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + connector.Command.CalculateAuthKeys(bike).Returns(x => throw new WebConnectFailureException("Context info", new Exception("Tst"))); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Unknown); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler bei Verbinden mit Schloss!", "Internet muss erreichbar sein um Verbindung mit Schloss für gemietetes Rad herzustellen.\r\nContext info\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("BookedDisconnected", handler.ButtonText); + Assert.IsFalse(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Booked unknown + /// + [Test] + public void TestSearchCalculateAuthKeysFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + connector.Command.CalculateAuthKeys(bike).Returns(x => throw new Exception("Exception message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Unknown); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler bei Verbinden mit Schloss!", "Kommunikationsfehler bei Schlosssuche.\r\nException message.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("BookedDisconnected", handler.ButtonText); + Assert.IsFalse(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Booked unknown + /// + [Test] + public void TestSearchConnectFailsOutOfReachException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new BookedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks.ConnectAsync(Arg.Any(), Arg.Any()) + .Returns>(x => { throw new OutOfReachException(); }); + locks.TimeOut.Returns(timeOuts); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + connector.Command.CalculateAuthKeys(bike); + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert( + "Fehler bei Verbinden mit Schloss!", + "Schloss kann erst gefunden werden, wenn gemietetes Rad in der Nähe ist.", + "Wiederholen", + "Abbrechen"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("BookedDisconnected", handler.ButtonText); + Assert.IsFalse(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Booked unknown + /// + [Test] + public void TestSearchConnectFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new BookedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks.ConnectAsync(Arg.Any(), Arg.Any()) + .Returns>(x => throw new Exception("Exception message.") ); + locks.TimeOut.Returns(timeOuts); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + connector.Command.CalculateAuthKeys(bike); + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAdvancedAlert( + "Fehler bei Verbinden mit Schloss!", + "Lock of rented bike can not be found.", + "Exception message.", + "Wiederholen", + "Abbrechen"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("BookedDisconnected", handler.ButtonText); + Assert.IsFalse(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Booked unknown + /// + [Test] + public void TestSearchConnectNotOpen() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new BookedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + + locks.ConnectAsync(Arg.Any(), Arg.Any()) + .Returns(new LockInfoTdo.Builder { State = null }.Build()); + locks.TimeOut.Returns(timeOuts); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + connector.Command.CalculateAuthKeys(bike); + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler bei Verbinden mit Schloss!", "Schlossstatus des gemieteten Rads konnte nicht ermittelt werden.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("BookedDisconnected", handler.ButtonText); + Assert.IsFalse(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedOpen.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedOpen.cs new file mode 100644 index 0000000..4e49c8b --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedOpen.cs @@ -0,0 +1,1157 @@ +using NUnit.Framework; +using NSubstitute; +using TINK.Model.Bikes.Bike.BluetoothLock; +using TINK.Model.Connector; +using TINK.Services.BluetoothLock; +using TINK.Model.Services.Geolocation; +using TINK.View; +using TINK.ViewModel; +using TINK.ViewModel.Bikes; +using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; +using System.Threading.Tasks; +using TINK.Model.State; +using Xamarin.Essentials; +using TINK.Services.BluetoothLock.Exception; +using TINK.Repository.Exception; +using System; +using TINK.Services.BluetoothLock.Tdo; +using TINK.Model.Bike.BluetoothLock; +using TINK.Model.User; +using TINK.Repository.Request; +using Newtonsoft.Json; +using TINK.Repository.Response; +using TINK.Model.Device; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler +{ + [TestFixture] + public class TestBookedOpen + { + /// + /// Test construction of object. + /// + [Test] + public void Testctor() + { + var handler = new BookedOpen( + Substitute.For(), + () => true, // isConnectedDelegate + (isConnexted) => Substitute.For(), + Substitute.For(), + Substitute.For(), + () => Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + + // Verify prerequisites. + Assert.AreEqual("Close lock & return bike", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual("Close lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Comment: User cancels operation. + /// Final state: Booked and open. + /// Similar to TestReservedClosed::TestBookAndOpenCancelBook + /// + [Test] + public void TestCloseAndReturnCancelReturn() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 Allround Mono abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(false)); + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Final state: Disposable closed. + /// Similar to TestReservedClosed::TestBookAndOpen + /// + [Test] + public void TestCloseAndReturn() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); // Return lock state indicating success + + geolocation.GetAsync(Arg.Any()).Returns(Task.FromResult(new Location(1, 2))); + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Return call leads to setting of state to disposable. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Abfrage Standort..."; + geolocation.GetAsync(Arg.Any()); // Geolocation must be retrieved + bikesViewModel.ActionText = "Gebe Rad zurück..."; + connector.Command.DoReturn(bike, Arg.Is(x => x.Latitude == 1 && x.Longitude ==2), Arg.Any()); // Booking must be performed + bikesViewModel.ActionText = "Disconnecting lock..."; + locks.DisconnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Disposable Closed" after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Final state: Same as initial state. + /// + [Test] + public void TestCloseAndReturnCloseFailsOutOfReachException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns< LockitLockingState?>(x => throw new OutOfReachException()); + + bike.LockInfo.State.Returns(LockingState.Open); // If locking fails bike remains open. + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking state remains unchanged if closing fails. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Lock can not be closed!", "Lock cannot be closed until bike is near.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Disconnected" after action + Assert.AreEqual("BookedDisconnected", subsequent.ButtonText); + Assert.IsFalse(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Final state: Same as initial state. + /// + [Test] + public void TestCloseAndReturnCloseFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns(x => throw new System.Exception("Blu")); + + bike.LockInfo.State.Returns(LockingState.Open); // If locking fails bike remains open. + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking state remains unchanged if closing fails. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Lock can not be closed!", "Blu", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked open" after action + Assert.AreEqual("BookedDisconnected", subsequent.ButtonText); + Assert.IsFalse(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Final state: Same as initial state. + /// + [Test] + public void TestCloseAndReturnCloseFailsNotOpen() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns(LockitLockingState.Open); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking state remains unchanged if closing fails. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Lock can not be closed!", "After try to close lock state open is reported.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked open" after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Final state: Booked locked. + /// + [Test] + public void TestCloseAndReturnGetGeolocationFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns(LockitLockingState.Closed); + + geolocation.GetAsync(Arg.Any()).Returns(x => throw new System.Exception("noloc")); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking state remains unchanged if closing fails. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Abfrage Standort..."; + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler bei Standortabfrage!", "Schloss schließen und Miete beenden ist nicht möglich.\r\nnoloc", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Final state: Booked locked. + /// + [Test] + public void TestCloseAndReturnReturnFailsWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns(LockitLockingState.Closed); + + connector.Command.DoReturn(bike, Arg.Any(), Arg.Any()).Returns(x => throw new WebConnectFailureException("Context info", new System.Exception("hoppla"))); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking state remains unchanged if closing fails. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Abfrage Standort..."; + bikesViewModel.ActionText = "Gebe Rad zurück..."; + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Verbingungsfehler beim Zurückgeben des Rads!", "Internet muss erreichbar sein beim Zurückgeben des Rads.\r\nContext info\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Final state: Booked locked. + /// + [Test] + public void TestCloseAndReturnReturnFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns(LockitLockingState.Closed); + + connector.Command.DoReturn(bike, Arg.Any(), Arg.Any()).Returns(x => throw new System.Exception("Exception message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking state remains unchanged if closing fails. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Abfrage Standort..."; + bikesViewModel.ActionText = "Gebe Rad zurück..."; + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler beim Zurückgeben des Rads!", "Exception message.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Final state: Booked locked. + /// + [Test] + public void TestCloseAndReturnReturnFailsNotAtStationException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns(LockitLockingState.Closed); + + NotAtStationException.IsNotAtStation("Failure 2178: bike 1545 out of GEO fencing. 15986 meter distance to next station 77. OK: bike 1545 locked confirmed", out NotAtStationException notAtStationException); + + connector.Command.DoReturn(bike, Arg.Any(), Arg.Any()).Returns(x => + throw notAtStationException); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking state remains unchanged if closing fails. + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Abfrage Standort..."; + bikesViewModel.ActionText = "Gebe Rad zurück..."; + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Error returning bike!", "Returning bike outside of station is not possible. Distance to station 77 is 15986 m.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Final state: Booked locked. + /// + [Test] + public void TestCloseAndReturnReturnFailsNoGPSDataException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns(LockitLockingState.Closed); + + NoGPSDataException.IsNoGPSData("Failure 2245: No GPS data, state change forbidden.", out NoGPSDataException noGPSDataException); + + connector.Command.DoReturn(bike, Arg.Any(), Arg.Any()).Returns(x => + throw noGPSDataException); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking state remains unchanged if closing fails. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Abfrage Standort..."; + bikesViewModel.ActionText = "Gebe Rad zurück..."; + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Error returning bike!", "Returning bike at an unknown location is not possible.\r\nBike can only be returned if bike is in reach and location information is available.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and return. + /// Final state: Booked locked. + /// + [Test] + public void TestCloseAndReturnReturnFailsResponseException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns(LockitLockingState.Closed); + + connector.Command.DoReturn(bike, Arg.Any(), Arg.Any()).Returns(x => + throw new ReturnBikeException(JsonConvert.DeserializeObject(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking state remains unchanged if closing fails. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Abfrage Standort..."; + bikesViewModel.ActionText = "Gebe Rad zurück..."; + bikesViewModel.ActionText = ""; + viewService.DisplayAdvancedAlert("Statusfehler beim Zurückgeben des Rads!", "Outer message.", "Some invalid data received!", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock + /// Final state: Booked closed. + /// + [Test] + public void TestClose() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].CloseAsync() + .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); // Return lock state indicating success + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock + /// Final state: Same as initial state. + /// + [Test] + public void TestCloseCloseFailsOutOfReachException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].CloseAsync() + .Returns>(x => throw new OutOfReachException()); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Open); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Lock can not be closed!", "Lock cannot be closed until bike is near.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked disconnected" after action + Assert.AreEqual("BookedDisconnected", subsequent.ButtonText); + Assert.IsFalse(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock + /// Final state: Same as initial state. + /// + [Test] + public void TestCloseCloseFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].CloseAsync() + .Returns>(x => throw new Exception("Exception message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Open); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Lock can not be closed!", "Exception message.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Disconnected" after action + Assert.AreEqual("BookedDisconnected", subsequent.ButtonText); + Assert.IsFalse(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock + /// Final state: Booked closed. + /// + [Test] + public void TestCloseUpdateLockingStateFailsWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].CloseAsync() + .Returns(LockitLockingState.Closed); // Return lock state indicating success + + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new WebConnectFailureException("Context info", new System.Exception("hoppla"))); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()); + bikesViewModel.ActionText = "No web error on updating locking status."; + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: close lock + /// Final state: Booked closed. + /// + [Test] + public void TestCloseUpdateLockingStateFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].CloseAsync() + .Returns(LockitLockingState.Closed); // Return lock state indicating success + + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new Exception("Exception message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()); + bikesViewModel.ActionText = "Connection error on updating locking status."; + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: close lock + /// Final state: Booked closed. + /// + [Test] + public void TestCloseUpdateLockingStateFailsResponseException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new BookedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks[0].CloseAsync() + .Returns(LockitLockingState.Closed); // Return lock state indicating success + + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => + throw new ReturnBikeException(JsonConvert.DeserializeObject(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks.Received()[0].CloseAsync(); // Lock must be closed + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, Arg.Any()); + bikesViewModel.ActionText = "Status error on updating lock state."; + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state "Booked Closed" after action + Assert.AreEqual("Return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestDisposableDisconnected.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestDisposableDisconnected.cs new file mode 100644 index 0000000..c1d55fc --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestDisposableDisconnected.cs @@ -0,0 +1,590 @@ +using NSubstitute; +using NUnit.Framework; +using System; +using System.Threading.Tasks; +using TINK.Model.Bike.BluetoothLock; +using TINK.Model.Bikes.Bike.BluetoothLock; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.Services.BluetoothLock; +using TINK.Services.BluetoothLock.Exception; +using TINK.Services.BluetoothLock.Tdo; +using TINK.Model.Services.Geolocation; +using TINK.Model.State; +using TINK.View; +using TINK.ViewModel; +using TINK.ViewModel.Bikes; +using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; +using TINK.Model.User; +using TINK.Model.Device; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler +{ + [TestFixture] + public class TestDisposableDisconnected + { + /// + /// Test construction of object. + /// + [Test] + public void Testctor() + { + var handler = new DisposableDisconnected( + Substitute.For(), + () => true, // isConnectedDelegate + (isConnexted) => Substitute.For(), + Substitute.For(), + Substitute.For(), + () => Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + + // Verify prerequisites. + Assert.AreEqual("Reserve bike", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", handler.LockitButtonText); + Assert.IsFalse(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Cancel reservation. + /// Final state: Same as initial state. + /// + [Test] + public void TestReserveAndConnectCancel() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new DisposableDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(false)); + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + viewService.DisplayAlert(string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No"); + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Reserve bike. + /// Final state: Reserved closed. + /// + [Test] + public void TestReserveAndConnect() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new DisposableDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); + + locks.ConnectAsync(Arg.Any(),Arg.Any()) + .Returns(Task.FromResult(new LockInfoTdo.Builder { State = LockitLockingState.Closed }.Build())); // Return lock state indicating success + + locks.TimeOut.Returns(timeOuts); + + viewService.DisplayAlert(string.Empty, string.Format("Rent bike {0} and open lock?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true)); + + locks[0].OpenAsync() + .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Open)); // Return lock state indicating success + + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking call leads to setting of state to booked. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reserving bike..."; + connector.Command.DoReserve(bike); // Booking must be performed + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = ""; + bikesViewModel.ActionText = "Renting bike..."; + connector.Command.DoBook(bike); // Booking must be performed + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be opened + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, null); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Reserve bike. + /// Final state: Same as initial state. + /// + [Test] + public void TestReserveAndConnectReserveFailsBookingDeclinedException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new DisposableDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); + + connector.Command.DoReserve(bike).Returns(x => throw new BookingDeclinedException(7)); // Booking must be performed + + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reserving bike..."; + connector.Command.DoReserve(bike); // Booking must be performed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Hint", string.Format("A reservation of bike {0} was rejected because the maximum allowed number of {1} reservations/ rentals had already been made.", 0, 7), "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Reserve bike. + /// Final state: Same as initial state. + /// + [Test] + public void TestReserveAndConnectReserveFailsWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new DisposableDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); + + connector.Command.DoReserve(bike).Returns(x => throw new WebConnectFailureException("Context info.", new Exception("chub"))); + + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reserving bike..."; + connector.Command.DoReserve(bike); // Booking must be performed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Verbingungsfehler beim Reservieren des Rads!", "Context info.\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Reserve bike. + /// Final state: Same as initial state. + /// + [Test] + public void TestReserveAndConnectReserveFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new DisposableDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); + + connector.Command.DoReserve(bike).Returns(x => throw new Exception("Exception message.")); + + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reserving bike..."; + connector.Command.DoReserve(bike); // Booking must be performed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler beim Reservieren des Rads!", "Exception message.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Reserve bike. + /// Final state: Reserved unknown. + /// + [Test] + public void TestReserveAndConnectConnectOutOfReachException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new DisposableDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); + + locks.ConnectAsync(Arg.Any(), Arg.Any()).Returns(x => throw new OutOfReachException()); + locks.TimeOut.Returns(timeOuts); + + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Disconnected); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reserving bike..."; + connector.Command.DoReserve(bike); // Booking must be performed + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Schloss außerhalb Reichweite"; + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Reserve bike. + /// Final state: Reserved unknown. + /// + [Test] + public void TestReserveAndConnectConnectException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new DisposableDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); + + locks.ConnectAsync(Arg.Any(), Arg.Any()).Returns(x => throw new Exception("Exception message.")); + locks.TimeOut.Returns(timeOuts); + + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Disconnected); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reserving bike..."; + connector.Command.DoReserve(bike); // Booking must be performed + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Schloss nicht gefunden"; + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Reserve bike. + /// Final state: Reserved unknown. + /// + [Test] + public void TestReserveAndConnectConnectStateUnknown() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new DisposableDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); + + locks.TimeOut.Returns(timeOuts); + + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Unknown); // Connect did not throw an exception but lock state is still unknown. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reserving bike..."; + connector.Command.DoReserve(bike); // Booking must be performed + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + [Test] + public void TestNotsupported() + { + var handler = new DisposableDisconnected( + Substitute.For(), + () => true, // isConnectedDelegate + (isConnexted) => Substitute.For(), + Substitute.For(), + Substitute.For(), + () => Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestDisposableOpen.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestDisposableOpen.cs new file mode 100644 index 0000000..523aaf6 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestDisposableOpen.cs @@ -0,0 +1,442 @@ +using NSubstitute; +using NUnit.Framework; +using System; +using System.Threading.Tasks; +using TINK.Model.Bike.BluetoothLock; +using TINK.Model.Bikes.Bike.BluetoothLock; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.Services.BluetoothLock; +using TINK.Services.BluetoothLock.Exception; +using TINK.Services.BluetoothLock.Tdo; +using TINK.Model.Services.Geolocation; +using TINK.Model.State; +using TINK.View; +using TINK.ViewModel; +using TINK.ViewModel.Bikes; +using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; +using TINK.Model.User; +using TINK.Model.Device; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler +{ + [TestFixture] + public class TestDisposableOpen + { + + /// + /// Test construction of object. + /// + [Test] + public void Testctor() + { + var handler = new DisposableOpen ( + Substitute.For(), + () => true, // isConnectedDelegate + (isConnexted) => Substitute.For(), + Substitute.For(), + Substitute.For(), + () => Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + + // Verify prerequisites. + Assert.AreEqual("Rent bike or close lock", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual(nameof(DisposableOpen), handler.LockitButtonText); + Assert.IsFalse(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Lock bike. + /// Final state: Disposable Closed. + /// + [Test] + public void TestCloseLock() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new DisposableOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(false)); + + locks[0].CloseAsync().Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = "Disconnecting lock..."; + locks.DisconnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Lock bike. + /// Final state: Same as initial state. + /// + [Test] + public void TestCloseLockCloseFailsOutOfReachExcption() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new DisposableOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(false)); + + locks[0].CloseAsync().Returns>(x => throw new OutOfReachException()); + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Open); + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Lock can not be closed!", "Lock cannot be closed until bike is near.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual(nameof(DisposableDisconnected), subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Lock bike. + /// Final state: Same as initial state. + /// + [Test] + public void TestCloseLockCloseFailsExcption() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new DisposableOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(false)); + + locks[0].CloseAsync().Returns>(x => throw new Exception("Exception message.")); + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Open); + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Lock can not be closed!", "Exception message.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual(nameof(DisposableDisconnected), subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Lock bike. + /// Final state: Booked Open. + /// + [Test] + public void TestDoBook() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new DisposableOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(true)); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Open); + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Renting bike..."; + connector.Command.DoBook(bike); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Lock bike. + /// Final state: Disposabled Closed. + /// + [Test] + public void TestDoBookDoBookFailsWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new DisposableOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(true)); + + connector.Command.DoBook(bike).Returns(x => throw new WebConnectFailureException("Context info.", new Exception("chub"))); + + locks[0].CloseAsync().Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); + + bike.State.Value.Returns(InUseStateEnum.Disposable); + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Renting bike..."; + connector.Command.DoBook(bike); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert( + "Connection error when renting the bike!", + string.Format("Attention: Lock is closed!\r\n{0}\r\n{1}", "Context info.", "Ist WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?"), + "OK"); + bikesViewModel.ActionText = "Verschließe Schloss..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = "Disconnecting lock..."; + locks.DisconnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Lock bike. + /// Final state: Disposabled Closed. + /// + [Test] + public void TestDoBookDoBookFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new DisposableOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(true)); + + connector.Command.DoBook(bike).Returns(x => throw new Exception("Exception message.")); + + locks[0].CloseAsync().Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); + + bike.State.Value.Returns(InUseStateEnum.Disposable); + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Renting bike..."; + connector.Command.DoBook(bike); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Error when renting the bike!", "Attention: Lock is closed!\r\nException message.", "OK"); + bikesViewModel.ActionText = "Verschließe Schloss..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = "Disconnecting lock..."; + locks.DisconnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestReservedDisconnected.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestReservedDisconnected.cs new file mode 100644 index 0000000..43a061c --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestReservedDisconnected.cs @@ -0,0 +1,752 @@ +using NSubstitute; +using NUnit.Framework; +using System; +using System.Threading.Tasks; +using TINK.Model.Bike.BluetoothLock; +using TINK.Model.Bikes.Bike.BluetoothLock; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.Services.BluetoothLock; +using TINK.Services.BluetoothLock.Exception; +using TINK.Services.BluetoothLock.Tdo; +using TINK.Model.Services.Geolocation; +using TINK.Model.State; +using TINK.View; +using TINK.ViewModel; +using TINK.ViewModel.Bikes; +using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; +using Newtonsoft.Json; +using TINK.Repository.Response; +using TINK.Model.User; +using TINK.Model.Device; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler +{ + public class TestReservedDisconnected + { + /// + /// Test construction of object. + /// + [Test] + public void Testctor() + { + var handler = new ReservedDisconnected( + Substitute.For(), + () => true, // isConnectedDelegate + (isConnexted) => Substitute.For(), + Substitute.For(), + Substitute.For(), + () => Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + + // Verify prerequisites. + Assert.AreEqual("Cancel bike reservation", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Cancel reservation. + /// Comment: User deceide to abort cancelling (user is ased whether to really cancel) + /// Final state: Same as initial state. + /// + [Test] + public void TestCancelReservationCancel() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, string.Format("Cancel reservation for bike {0}?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(false)); + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + viewService.DisplayAlert(string.Empty, string.Format("Cancel reservation for bike {0}?", "Nr. 0"), "Yes", "No"); + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Cancel reservation. + /// Final state: Disposable. + /// + [Test] + public void TestCancelReservation() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, string.Format("Cancel reservation for bike {0}?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true)); + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Disconnected); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Canceling reservation..."; + connector.Command.DoCancelReservation(bike); // Booking must be performed + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Cancel reservation. + /// Comment: Exception is thrown. + /// Final state: Same as initial state. + /// + [Test] + public void TestCancelReservationDoCancelReservationInvalidAuthorizationResponseException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, string.Format("Cancel reservation for bike {0}?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true)); + + var l_oResponse = JsonConvert.DeserializeObject(@" + { + ""response"" : ""authorization"", + ""authcookie"" : ""4da3044c8657a04ba60e2eaa753bc51a"", + ""user_group"" : [ ""TINK"", ""Konrad"" ], + ""response_state"" : ""OK"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"" + }"); + + connector.Command.DoCancelReservation(bike).Returns(x => throw new InvalidAuthorizationResponseException("mustermann@server.de", l_oResponse)); + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Disconnected); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Canceling reservation..."; + connector.Command.DoCancelReservation(bike); // Booking must be performed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler beim Aufheben der Reservierung!", "Kann Benutzer mustermann@server.de nicht anmelden. Mailadresse unbekannt oder Passwort ungültig.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Cancel reservation. + /// Comment: Canceling reservation fails. + /// Final state: Same as initial state. + /// + [Test] + public void TestCancelReservationDoCancelReservationWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, string.Format("Cancel reservation for bike {0}?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true)); + + connector.Command.DoCancelReservation(bike).Returns(x => throw new WebConnectFailureException("Context info", new Exception("chub"))); + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Disconnected); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Canceling reservation..."; + connector.Command.DoCancelReservation(bike); // Booking must be performed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Verbingungsfehler beim Aufheben der Reservierung!", "Context info\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Cancel reservation. + /// Comment: Canceling reservation fails. + /// Final state: Same as initial state. + /// + [Test] + public void TestCancelReservationDoCancelReservationException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, string.Format("Cancel reservation for bike {0}?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true)); + + connector.Command.DoCancelReservation(bike).Returns(x => throw new Exception("Exception message.")); + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Disconnected); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Canceling reservation..."; + connector.Command.DoCancelReservation(bike); // Booking must be performed + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler beim Aufheben der Reservierung!", "Exception message.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: reserved closed + /// + [Test] + public void TestSearch() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + locks.ConnectAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(new LockInfoTdo.Builder { State = LockitLockingState.Closed }.Build())); + + locks.TimeOut.Returns(timeOuts); + + viewService.DisplayAlert(string.Empty, string.Format("Rent bike {0} and open lock?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true)); + + locks[0].OpenAsync() + .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Open)); // Return lock state indicating success + + bike.State.Value.Returns(InUseStateEnum.Booked); // Booking call leads to setting of state to booked. + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + connector.Command.CalculateAuthKeys(bike); + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = ""; + bikesViewModel.ActionText = "Renting bike..."; + connector.Command.DoBook(bike); // Booking must be performed + bikesViewModel.ActionText = "Opening lock..."; + locks.Received()[0].OpenAsync(); // Lock must be opened + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Updating lock state..."; + connector.Command.UpdateLockingStateAsync(bike, null); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Same as initial state. + /// + [Test] + public void TestSearchCalculateAuthKeysFailsWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + connector.Command.CalculateAuthKeys(bike).Returns(x => throw new WebConnectFailureException("Context info.", new Exception("Tst"))); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Unknown); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler bei Verbinden mit Schloss!", "Internet muss erreichbar sein um Verbindung mit Schloss für reserviertes Rad herzustellen.\r\nContext info.\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Same as initial state. + /// + [Test] + public void TestSearchCalculateAuthKeysFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + connector.Command.CalculateAuthKeys(bike).Returns(x => throw new Exception("Exception message.")); + + bike.State.Value.Returns(InUseStateEnum.Booked); + bike.LockInfo.State.Returns(LockingState.Unknown); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler bei Verbinden mit Schloss!", "Kommunikationsfehler bei Schlosssuche.\r\nException message.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Same as initial state. + /// + [Test] + public void TestSearchConnectFailsOutOfReachException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks.ConnectAsync(Arg.Any(), Arg.Any()) + .Returns>(x => { throw new OutOfReachException(); }); + locks.TimeOut.Returns(timeOuts); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + connector.Command.CalculateAuthKeys(bike); + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert( + "Fehler bei Verbinden mit Schloss!", + "Schloss kann erst gefunden werden, wenn reserviertes Rad in der Nähe ist.", + "Wiederholen", + "Abbrechen"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Same as initial state. + /// + [Test] + public void TestSearchConnectFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + + locks.ConnectAsync(Arg.Any(), Arg.Any()) + .Returns>(x => throw new Exception("Execption message.")); + locks.TimeOut.Returns(timeOuts); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + connector.Command.CalculateAuthKeys(bike); + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAdvancedAlert( + "Fehler bei Verbinden mit Schloss!", + "Lock of reserved bike can not be found.", + "Execption message.", + "Wiederholen", + "Abbrechen"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + + /// + /// Use case: Search. + /// Final state: Same as initial state. + /// + [Test] + public void TestSearchConnectNotOpen() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + var timeOuts = Substitute.For(); + + var handler = new ReservedDisconnected( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + locks.ConnectAsync(Arg.Any(), Arg.Any()) + .Returns(new LockInfoTdo.Builder { State = null }.Build()); + locks.TimeOut.Returns(timeOuts); + + bike.State.Value.Returns(InUseStateEnum.Booked); + + var subsequent = handler.HandleRequestOption2().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Request server..."; + connector.Command.CalculateAuthKeys(bike); + bikesViewModel.ActionText = "Searching lock..."; + locks.ConnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler bei Verbinden mit Schloss!", "Schlossstatus des reservierten Rads konnte nicht ermittelt werden.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual("Search lock", handler.LockitButtonText); + Assert.IsTrue(handler.IsLockitButtonVisible); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestReservedOpen.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestReservedOpen.cs new file mode 100644 index 0000000..cfce3da --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestReservedOpen.cs @@ -0,0 +1,671 @@ +using Newtonsoft.Json; +using NSubstitute; +using NUnit.Framework; +using System; +using System.Threading.Tasks; +using TINK.Model.Bike.BluetoothLock; +using TINK.Model.Bikes.Bike.BluetoothLock; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.Repository.Response; +using TINK.Services.BluetoothLock; +using TINK.Services.BluetoothLock.Exception; +using TINK.Services.BluetoothLock.Tdo; +using TINK.Model.Services.Geolocation; +using TINK.Model.State; +using TINK.View; +using TINK.ViewModel; +using TINK.ViewModel.Bikes; +using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; +using TINK.Model.User; +using TINK.Model.Device; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler +{ + [TestFixture] + public class TestReservedOpen + { + /// + /// Test construction of object. + /// + [Test] + public void Testctor() + { + var handler = new ReservedOpen( + Substitute.For(), + () => true, // isConnectedDelegate + (isConnexted) => Substitute.For(), + Substitute.For(), + Substitute.For(), + () => Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + + // Verify prerequisites. + Assert.AreEqual("Rad zurückgeben oder mieten", handler.ButtonText); + Assert.IsTrue(handler.IsButtonVisible); + Assert.AreEqual("Alarm/ Sounds verwalten", handler.LockitButtonText); + Assert.IsFalse(handler.IsLockitButtonVisible); + } + + /// + /// Use case: User books bike. + /// Final state: Booked. + /// + [Test] + public void TestBook() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + viewService.DisplayAlert(string.Empty, "Rad Nr. 0 abschließen und zurückgeben oder Rad mieten?", "Zurückgeben", "Mieten").Returns(Task.FromResult(false)); + + bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Open); // Requsthandler factory queries lock state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Renting bike..."; + connector.Command.DoBook(bike); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Close lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: User books bike. + /// Final state: Reserved closed. + /// + [Test] + public void TestBookBookFailsWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + viewService.DisplayAlert(string.Empty, "Rad Nr. 0 abschließen und zurückgeben oder Rad mieten?", "Zurückgeben", "Mieten").Returns(Task.FromResult(false)); + + connector.Command.DoBook(bike).Returns(x => throw new WebConnectFailureException("Context info.", new Exception("Tst"))); + + locks[0].CloseAsync().Returns(LockitLockingState.Closed); + + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Renting bike..."; + connector.Command.DoBook(bike); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert( + "Connection error when renting the bike!", + string.Format("Attention: Lock is closed!\r\n{0}\r\n{1}", "Context info.", "Ist WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?"), + "OK"); + bikesViewModel.ActionText = "Wiederverschließe Schloss..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & rent bike", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: User books bike. + /// Final state: Reserved closed. + /// + [Test] + public void TestBookBookFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + viewService.DisplayAlert(string.Empty, "Rad Nr. 0 abschließen und zurückgeben oder Rad mieten?", "Zurückgeben", "Mieten").Returns(Task.FromResult(false)); + + connector.Command.DoBook(bike).Returns(x => throw new Exception("Exception message.")); + + locks[0].CloseAsync().Returns(LockitLockingState.Closed); + + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Reading charging level..."; + locks[0].GetBatteryPercentageAsync(); + bikesViewModel.ActionText = "Renting bike..."; + connector.Command.DoBook(bike); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Error when renting the bike!", "Attention: Lock is closed!\r\nException message.", "OK"); + bikesViewModel.ActionText = "Wiederverschließe Schloss..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Open lock & rent bike", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and cancel reservation. + /// Final state: Booked. + /// + [Test] + public void TestCloseLockAndCancelReservation() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Rad Nr. 0 abschließen und zurückgeben oder Rad mieten?", "Zurückgeben", "Mieten").Returns(Task.FromResult(true)); + + locks[0].CloseAsync().Returns(LockitLockingState.Closed); + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = "Canceling reservation..."; + connector.Command.DoCancelReservation(bike); + bikesViewModel.ActionText = "Disconnecting lock..."; + locks.DisconnectAsync(Arg.Any(), Arg.Any()); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Reserve bike", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and cancel reservation. + /// Final state: Reserved open. + /// + [Test] + public void TestCloseLockAndCancelReservationCloseFailsOutOfReachException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert( + string.Empty, + "Rad Nr. 0 abschließen und zurückgeben oder Rad mieten?", + "Zurückgeben", + "Mieten").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns>(x => { throw new OutOfReachException(); }); + + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Open); + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert( + "Lock can not be closed!", + "Lock cannot be closed until bike is near.\r\nPlease try again to close bike or report bike to support!", + "OK"); + + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and cancel reservation. + /// Final state: Same as initial state. + /// + [Test] + public void TestCloseLockAndCancelReservationCloseFailsException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert( + string.Empty, + "Rad Nr. 0 abschließen und zurückgeben oder Rad mieten?", + "Zurückgeben", + "Mieten").Returns(Task.FromResult(true)); + + locks[0].CloseAsync() + .Returns>(x => { throw new Exception("Exception message."); }); + + bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. + bike.LockInfo.State.Returns(LockingState.Open); + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert( + "Lock can not be closed!", + "Please try to lock again or report bike to support!\r\nException message.", + "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("Cancel bike reservation", subsequent.ButtonText); + Assert.IsTrue(subsequent.IsButtonVisible); + Assert.AreEqual("Search lock", subsequent.LockitButtonText); + Assert.IsTrue(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and cancel reservation. + /// Final state: Reserved closed. + /// + [Test] + public void TestCloseLockAndCancelReservationCancelReservationInvalidAuthorizationResponseException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + var response = JsonConvert.DeserializeObject(@" + { + ""response"" : ""authorization"", + ""authcookie"" : ""4da3044c8657a04ba60e2eaa753bc51a"", + ""user_group"" : [ ""TINK"", ""Konrad"" ], + ""response_state"" : ""OK"", + ""apiserver"" : ""https://tinkwwp.copri-bike.de"" + }"); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Rad Nr. 0 abschließen und zurückgeben oder Rad mieten?", "Zurückgeben", "Mieten").Returns(Task.FromResult(true)); + + locks[0].CloseAsync().Returns(LockitLockingState.Closed); + + connector.Command.DoCancelReservation(bike).Returns(x => throw new InvalidAuthorizationResponseException("mustermann@server.de", response)); + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + var subsequent = handler.HandleRequestOption1().Result; + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = "Canceling reservation..."; + connector.Command.DoCancelReservation(bike); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler beim Aufheben der Reservierung!", "Kann Benutzer mustermann@server.de nicht anmelden. Mailadresse unbekannt oder Passwort ungültig.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("InvalidState", subsequent.ButtonText); + Assert.IsFalse(subsequent.IsButtonVisible); + Assert.AreEqual("InvalidState", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and cancel reservation. + /// Final state: Reserved closed. + /// + [Test] + public void TestCloseLockAndCancelReservationCancelReservationWebConnectFailureException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Rad Nr. 0 abschließen und zurückgeben oder Rad mieten?", "Zurückgeben", "Mieten").Returns(Task.FromResult(true)); + + locks[0].CloseAsync().Returns(LockitLockingState.Closed); + + connector.Command.DoCancelReservation(bike).Returns(x => throw new WebConnectFailureException("Context info", new Exception("chub"))); + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = "Canceling reservation..."; + connector.Command.DoCancelReservation(bike); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Verbingungsfehler beim Aufheben der Reservierung!", "Context info\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("InvalidState", subsequent.ButtonText); + Assert.IsFalse(subsequent.IsButtonVisible); + Assert.AreEqual("InvalidState", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + + /// + /// Use case: Close lock and cancel reservation. + /// Final state: Reserved closed. + /// + [Test] + public void TestCloseLockAndCancelReservationCancelReservationException() + { + var bike = Substitute.For(); + var connector = Substitute.For(); + var command = Substitute.For(); + var geolocation = Substitute.For(); + var locks = Substitute.For(); + var pollingManager = Substitute.For(); + var viewService = Substitute.For(); + var bikesViewModel = Substitute.For(); + var activeUser = Substitute.For(); + + var handler = new ReservedOpen( + bike, + () => true, // isConnectedDelegate + (isConnexted) => connector, + geolocation, + locks, + () => pollingManager, + Substitute.For(), + viewService, + bikesViewModel, + activeUser); + + bike.Id.Returns("0"); + + viewService.DisplayAlert(string.Empty, "Rad Nr. 0 abschließen und zurückgeben oder Rad mieten?", "Zurückgeben", "Mieten").Returns(Task.FromResult(true)); + + locks[0].CloseAsync().Returns(LockitLockingState.Closed); + + connector.Command.DoCancelReservation(bike).Returns(x => throw new Exception("Exception message.", new Exception("chub"))); + + bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. + + var subsequent = handler.HandleRequestOption1().Result; + + locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); + + // Verify behaviour + Received.InOrder(() => + { + bikesViewModel.Received(1).IsIdle = false; // GUI must be locked + bikesViewModel.ActionText = "One moment please..."; + pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action + bikesViewModel.ActionText = "Closing lock..."; + locks[0].CloseAsync(); + bikesViewModel.ActionText = "Canceling reservation..."; + connector.Command.DoCancelReservation(bike); + bikesViewModel.ActionText = ""; + viewService.DisplayAlert("Fehler beim Aufheben der Reservierung!", "Exception message.", "OK"); + bikesViewModel.ActionText = "Updating..."; + pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again + bikesViewModel.ActionText = ""; + bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked + }); + + // Verify state after action + Assert.AreEqual("InvalidState", subsequent.ButtonText); + Assert.IsFalse(subsequent.IsButtonVisible); + Assert.AreEqual("InvalidState", subsequent.LockitButtonText); + Assert.IsFalse(subsequent.IsLockitButtonVisible); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/TestRequestHandlerFactory.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/TestRequestHandlerFactory.cs new file mode 100644 index 0000000..79e05fe --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/TestRequestHandlerFactory.cs @@ -0,0 +1,145 @@ +using NUnit.Framework; +using Rhino.Mocks; +using System; +using System.Collections.Generic; +using TestTINKLib.Mocks.Services; +using TINK.Model.Bike.BluetoothLock; +using TINK.Model.Device; +using TINK.Model.State; +using TINK.Model.User; +using TINK.ViewModel.Bikes; +using TINK.ViewModel.Bikes.Bike.BluetoothLock; +using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; + +namespace TestTINKLib.Fixtures.ObjectTests.Bike.BluetoothLock +{ + /// + /// Moved to TestShareeLib (.Net Core) + /// + [TestFixture] + public class TestRequestHandlerFactory + { + [Test] + public void TestCreate() + { + // Verify handler for disposable bike. + var bike = new BikeInfoMutable(new BikeInfo("22", 5200544, new Guid("00000000-0000-0000-0000-000000000001"), "12")); + Assert.AreEqual(InUseStateEnum.Disposable, bike.State.Value); + Assert.AreEqual(LockingState.Disconnected, bike.LockInfo.State); + Assert.AreEqual( + typeof(DisposableDisconnected), + RequestHandlerFactory.Create( + bike, + () => false, // isConnectedDelegate + (connected) => null, // connectorFactory + new GeolocationMock(), // geolocation + new LocksServiceMock(), // LockService + null, // viewUpdateManager + NSubstitute.Substitute.For(), + null /* viewService */ , + MockRepository.GenerateStub(), + MockRepository.GenerateStub()).GetType()); + + // Verify handler for requested bike with state unknown. + bike = new BikeInfoMutable(new BikeInfo("22", 0 /* lock Id */, new Guid(), /*K User*/ null, /*K Admin*/ null, /*K Seed*/ null, new DateTime(2020, 1, 1), "a@b.com", "12", null, null, () => new DateTime(2020, 1, 1), false, new List(), TINK.Model.Bike.WheelType.Mono, TINK.Model.Bike.TypeOfBike.Allround)); + Assert.AreEqual( + typeof(ReservedDisconnected), + RequestHandlerFactory.Create( + bike, + () => false, // isConnectedDelegate + (connected) => null, // connectorFactory + new GeolocationMock(), // geolocation + new LocksServiceMock(), // LockService + null, // viewUpdateManager + NSubstitute.Substitute.For(), + null /* viewService */, + MockRepository.GenerateStub(), + MockRepository.GenerateStub()).GetType()); + + // Verify handler for requested bike with state closed. + bike = new BikeInfoMutable(new BikeInfo("22", 0 /* lock Id*/, new Guid(), null, null, null, new System.DateTime(2020, 1, 1), "a@b.com", "12", null, null, () => new DateTime(2020, 1, 1), false, new List(), TINK.Model.Bike.WheelType.Mono, TINK.Model.Bike.TypeOfBike.Allround)); + bike.LockInfo.State = LockingState.Closed; + Assert.AreEqual( + typeof(ReservedClosed), + RequestHandlerFactory.Create( + bike, + () => false, // isConnectedDelegate + (connected) => null, // connectorFactory + new GeolocationMock(), // geolocation + new LocksServiceMock(), // LockService + null, // viewUpdateManager + NSubstitute.Substitute.For(), + null /* viewService */, + MockRepository.GenerateStub(), + MockRepository.GenerateStub()).GetType()); + + // Verify handler for requested bike with state open. + bike = new BikeInfoMutable(new BikeInfo("22", 0 /* lock Id*/, new Guid(), null, null, null, new DateTime(2020, 1, 1), "a@b.com", "12", null, null, () => new DateTime(2020, 1, 1), false, new List(), TINK.Model.Bike.WheelType.Mono, TINK.Model.Bike.TypeOfBike.Allround)); + bike.LockInfo.State = LockingState.Open; + Assert.AreEqual( + typeof(ReservedOpen), + RequestHandlerFactory.Create( + bike, + () => false, // isConnectedDelegate + (connected) => null, // connectorFactory + new GeolocationMock(), // geolocation + new LocksServiceMock(), // LockService + null, // viewUpdateManager + NSubstitute.Substitute.For(), + null /* viewService */, + MockRepository.GenerateStub(), + MockRepository.GenerateStub()).GetType()); + + // Verify handler for booked bike with state closed. + bike = new BikeInfoMutable(new BikeInfo("22", 0 /* lock Id*/, new Guid(), null, null, null, new System.DateTime(2020, 1, 1), "a@b.com", "12", null /*operator uri*/, null, false, new List(), TINK.Model.Bike.WheelType.Mono, TINK.Model.Bike.TypeOfBike.Allround)); + bike.LockInfo.State = LockingState.Closed; + Assert.AreEqual( + typeof(BookedClosed), + RequestHandlerFactory.Create( + bike, + () => false, // isConnectedDelegate + (connected) => null, // connectorFactory + new GeolocationMock(), // geolocation + new LocksServiceMock(), // LockService + null, // viewUpdateManager + NSubstitute.Substitute.For(), + null /* viewService */, + MockRepository.GenerateStub(), + MockRepository.GenerateStub()).GetType()); + + // Verify handler for booked bike with state open. + bike = new BikeInfoMutable(new BikeInfo("22", 0 /* lock Id*/, new Guid(), null, null, null, new System.DateTime(2020, 1, 1), "a@b.com", "12", null /*operator uri*/, null, false, new List(), TINK.Model.Bike.WheelType.Mono, TINK.Model.Bike.TypeOfBike.Allround)); + bike.LockInfo.State = LockingState.Open; + Assert.AreEqual( + typeof(BookedOpen), + RequestHandlerFactory.Create( + bike, + () => false, // isConnectedDelegate + (connected) => null, // connectorFactory + new GeolocationMock(), // geolocation + new LocksServiceMock(), // LockService + null, // viewUpdateManager + NSubstitute.Substitute.For(), + null /* viewService */, + MockRepository.GenerateStub(), + MockRepository.GenerateStub()).GetType()); + + // Verify handler for booked bike with state unknown. + bike = new BikeInfoMutable(new BikeInfo("22", 0 /* lock Id*/, new Guid(), null, null, null, new System.DateTime(2020, 1, 1), "a@b.com", "12", null /*operator uri*/, null, false, new List(), TINK.Model.Bike.WheelType.Mono, TINK.Model.Bike.TypeOfBike.Allround)); + + Assert.AreEqual( + typeof(BookedDisconnected), + RequestHandlerFactory.Create( + bike, + ()=> false, // isConnectedDelegate + (connected) => null, // connectorFactory + new GeolocationMock(), // geolocation + new LocksServiceMock(), // LockService + null, // viewUpdateManager + NSubstitute.Substitute.For(), + null /* viewService */, + MockRepository.GenerateStub(), + MockRepository.GenerateStub()).GetType()); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/CopriWebView/TestManageAccountViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/CopriWebView/TestManageAccountViewModel.cs new file mode 100644 index 0000000..d2a5110 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/CopriWebView/TestManageAccountViewModel.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using TINK.ViewModel.Login; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.CopriWebView +{ + [TestFixture] + public class TestManageAccountViewModel + { + [Test] + public void TestUrl() + { + var viewModel = new ManageAccountViewModel("Keks", "Merchant", "Hosti"); + Assert.AreEqual("https://Hosti?sessionid=KeksMerchant", viewModel.Uri); + } + } +} \ No newline at end of file diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/CopriWebView/TestPasswordForgottonViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/CopriWebView/TestPasswordForgottonViewModel.cs new file mode 100644 index 0000000..aff5778 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/CopriWebView/TestPasswordForgottonViewModel.cs @@ -0,0 +1,30 @@ +using NUnit.Framework; +using TINK.ViewModel.CopriWebView; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.CopriWebView +{ + [TestFixture] + public class TestPasswordForgottonViewModel + { + [Test] + public void TestUrl() + { + var viewModel = new PasswordForgottonViewModel("Merchant", "Hosti"); + Assert.AreEqual("https://Hosti/app/Account?sessionid=Merchant", viewModel.Uri); + } + + [Test] + public void TestUrl_TINKLive() + { + var viewModel = new PasswordForgottonViewModel("Merchant", "app.tink-konstanz.de"); + Assert.AreEqual("https://app.tink-konstanz.de/tinkapp/Account?sessionid=Merchant", viewModel.Uri); + } + + [Test] + public void TestUrl_TINK() + { + var viewModel = new PasswordForgottonViewModel("Merchant", "tinkwwp.copri-bike.de"); + Assert.AreEqual("https://tinkwwp.copri-bike.de/tinkapp/Account?sessionid=Merchant", viewModel.Uri); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Info/TestInfoViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Info/TestInfoViewModel.cs new file mode 100644 index 0000000..bfab5e2 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Info/TestInfoViewModel.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using TINK.ViewModel.Info; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Info +{ + [TestFixture] + public class TestInfoViewModel + { + [Test] + public void TestOnAppearing() + { + var viewModel = new InfoViewModel("Hosti", false, (url) => string.Empty); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Map/TestMapPageFilter.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Map/TestMapPageFilter.cs new file mode 100644 index 0000000..bf11028 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Map/TestMapPageFilter.cs @@ -0,0 +1,162 @@ +using NUnit.Framework; +using System.Collections.Generic; +using TINK.Model; + +using TINK.ViewModel.Map; +namespace UITest.Fixtures.ObjectTests.ViewModel.Map +{ + + [TestFixture] + public class TestMapPageViewModel + { + /// + /// Verifies that if Konrad is turned off in settings map page filter does no more contain Konrad option. + /// + [Test] + public void TestGetFilterDictinaryMapPage_NoKonrad_TinkOnKonradOff() + { + var l_oDict = GroupFilterMapPageHelper.CreateUpdated( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), // Last map page filter (Konrad was still available but off) + new List { "TINK" }); // Filters from settings page. + + Assert.AreEqual(1, l_oDict.Count); + Assert.IsTrue(l_oDict.ContainsKey("TINK")); + Assert.AreEqual(FilterState.On, l_oDict["TINK"]); + } + + /// + /// Verifies that if Konrad is turned off in settings map page filter does no more contain Konrad option. + /// + [Test] + public void TestGetFilterDictinaryMapPage_NoKonrad_TinkOffKonradOn() + { + var l_oDict = GroupFilterMapPageHelper.CreateUpdated( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.Off }, { "Konrad", FilterState.On } }), // Last map page filter (Konrad was still available but off) + new List { "TINK" }); // Filters from settings page. + + Assert.AreEqual(1, l_oDict.Count); + Assert.IsTrue(l_oDict.ContainsKey("TINK")); + Assert.AreEqual(FilterState.On, l_oDict["TINK"]); + } + + /// + /// Verifies that if TINK.* is turned off in settings map page filter does no more contain TINK option. + /// + [Test] + public void TestGetFilterDictinaryMapPage_NoTink_TinkOnKonradOff() + { + var l_oDict = GroupFilterMapPageHelper.CreateUpdated( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), // Last map page filter (Konrad was still available but off) + new List { "Konrad" }); // Filters from settings page. + + Assert.AreEqual(1, l_oDict.Count); + Assert.IsTrue(l_oDict.ContainsKey("Konrad")); + Assert.AreEqual(FilterState.On, l_oDict["Konrad"]); + } + + /// + /// Verifies that if Konrad is turned on in settings map page filter is updated with entry Konrad. + /// + [Test] + public void TestGetFilterDictinaryMapPage_TinkOn() + { + var l_oDict = GroupFilterMapPageHelper.CreateUpdated( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On } }), // Last map page filter (Konrad was still available but off) + new List { "TINK", "Konrad" }); // Filters from settings page. + + Assert.AreEqual(2, l_oDict.Count); + Assert.IsTrue(l_oDict.ContainsKey("TINK")); + Assert.AreEqual(FilterState.Off, l_oDict["TINK"]); + Assert.IsTrue(l_oDict.ContainsKey("Konrad")); + Assert.AreEqual(FilterState.On, l_oDict["Konrad"]); + } + + /// + /// Verifies that if Konrad is turned on in settings map page filter is updated with entry Konrad. + /// + [Test] + public void TestGetFilterDictinaryMapPage_TinkOff() + { + var l_oDict = GroupFilterMapPageHelper.CreateUpdated( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.Off } }), // Last map page filter (Konrad was still available but off) + new List { "TINK", "Konrad" }); // Filters from settings page. + + Assert.AreEqual(2, l_oDict.Count); + Assert.IsTrue(l_oDict.ContainsKey("TINK")); + Assert.AreEqual(FilterState.Off, l_oDict["TINK"]); + Assert.IsTrue(l_oDict.ContainsKey("Konrad")); + Assert.AreEqual(FilterState.On, l_oDict["Konrad"]); + } + + /// + /// Verifies that map page filters are not touched if state is consitend. + /// + [Test] + public void TestGetFilterDictinaryMapPage_AllOn_KonradActivated() + { + var l_oDict = GroupFilterMapPageHelper.CreateUpdated( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.Off }, { "Konrad", FilterState.On } }), // Last map page filter (Konrad was still available but off) + new List { "TINK", "Konrad" }); // Filters from settings page. + + Assert.AreEqual(2, l_oDict.Count); + Assert.IsTrue(l_oDict.ContainsKey("TINK")); + Assert.AreEqual(FilterState.Off, l_oDict["TINK"]); + Assert.IsTrue(l_oDict.ContainsKey("Konrad")); + Assert.AreEqual(FilterState.On, l_oDict["Konrad"]); + } + + /// + /// Verifies that map page filters are not touched if state is consitend.. + /// + [Test] + public void TestGetFilterDictinaryMapPage_AllOn_TinkActivated() + { + var l_oDict = GroupFilterMapPageHelper.CreateUpdated( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), // Last map page filter (Konrad was still available but off) + new List { "TINK", "Konrad" }); // Filters from settings page. + + Assert.AreEqual(2, l_oDict.Count); + Assert.IsTrue(l_oDict.ContainsKey("TINK")); + Assert.AreEqual(FilterState.On, l_oDict["TINK"]); + Assert.IsTrue(l_oDict.ContainsKey("Konrad")); + Assert.AreEqual(FilterState.Off, l_oDict["Konrad"]); + } + + /// + /// Verifies that map page filters are not touched if state is consitend. + /// + [Test] + public void TestGetFilterDictinaryMapPage_NullFilter() + { + var l_oDict = GroupFilterMapPageHelper.CreateUpdated( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), // Last map page filter (Konrad was still available but off) + null); + + Assert.AreEqual(2, l_oDict.Count, "Do not apply any filter if filter value null is detected."); + Assert.IsTrue(l_oDict.ContainsKey("TINK")); + Assert.AreEqual(FilterState.On, l_oDict["TINK"]); + Assert.IsTrue(l_oDict.ContainsKey("Konrad")); + Assert.AreEqual(FilterState.Off, l_oDict["Konrad"]); + + l_oDict = GroupFilterMapPageHelper.CreateUpdated( + null, + null); + + Assert.IsNull(l_oDict, "Do not apply any filter if filter value null is detected."); + } + + [Test] + public void TestDoToggle() + { + var l_oFilter = new TinkKonradToggleViewModel(new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } })); + + l_oFilter = new TinkKonradToggleViewModel(l_oFilter.FilterDictionary). DoToggle(); + + Assert.AreEqual("Konrad", l_oFilter.CurrentFilter); + + l_oFilter = new TinkKonradToggleViewModel(l_oFilter.FilterDictionary).DoToggle(); + + Assert.AreEqual("TINK", l_oFilter.CurrentFilter); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Map/TestMapPageViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Map/TestMapPageViewModel.cs new file mode 100644 index 0000000..15f25e1 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Map/TestMapPageViewModel.cs @@ -0,0 +1,528 @@ +using NUnit.Framework; +using Rhino.Mocks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using TestTINKLib.Mocks.Connector; +using TestTINKLib.Mocks.Device; +using TestTINKLib.Mocks.Services; +using TestTINKLib.Model.User.Account; +using TINK.Model; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.Model.Services.CopriApi; +using TINK.Repository; +using TINK.View; +using TINK.ViewModel.Map; +using Xamarin.Forms; +using static TINK.Repository.CopriCallsMemory; +using TINK.ViewModel.Settings; +using TINK.Model.Services.Geolocation; +using TINK.Services; +using NSubstitute; + +namespace TestTINKLib.Fixtures.UseCases.Startup +{ + + [TestFixture] + public class TestMapPageViewModel + { + [Test] + public async Task TestConstruct() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://tinkwwp.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var viewService = MockRepository.GenerateStub(); + var navigationService = MockRepository.GenerateStub(); + + var viewModel = new MapPageViewModel( + tinkApp, + new PermissionsMock(), + NSubstitute.Substitute.For(), + NSubstitute.Substitute.For(), + (mapspan) => { }, + viewService, + navigationService); + + try + { + await viewModel.OnAppearing(); // Is called when page shows. + + Assert.IsNull(viewModel.Exception); + + // Verify pins on map + Assert.AreEqual(8, viewModel.Pins.Count); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "4").Icon.Id.Contains("Green"), + "Station 4 must be marked green because there is are bike."); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "31").Icon.Id.Contains("Red"), + "Station 31 must be marked red because there is no bike."); + + // Verify buttons + Assert.IsTrue(viewModel.IsToggleVisible, "TINK and Konrad are activated in settings."); + Assert.AreEqual(Color.Blue, viewModel.TinkColor, "TINK bikes are shown."); + + Assert.AreEqual(Color.Gray, viewModel.KonradColor, "Konrad bikes are hidden."); + + var statusInfoText = viewModel.StatusInfoText; + Assert.That( + statusInfoText, + Does.Contain("Updating...").Or.Contain(""), + $"Unexpected text {statusInfoText} detected.", + "Text might be \"Updating...\" or empty depending on acivity of update thread."); + } + finally + { + await viewModel.OnDisappearing(); + } + } + + [Test] + public async Task TestConstruct_KonradActive() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.Off }, { "Konrad", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://tinkwwp.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var viewService = MockRepository.GenerateStub(); + var navigationService = MockRepository.GenerateStub(); + + var viewModel = new MapPageViewModel( + tinkApp, + new PermissionsMock(), + NSubstitute.Substitute.For(), + NSubstitute.Substitute.For(), + (mapspan) => { }, + viewService, + navigationService); + + try + { + await viewModel.OnAppearing(); // Is called when page shows. + + Assert.IsNull(viewModel.Exception); + + // Verify pins on map + Assert.AreEqual(2, viewModel.Pins.Count); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "31").Icon.Id.Contains("Green"), + "Station 5 must be marked green because there is are bike."); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "14").Icon.Id.Contains("Red"), + "Station 14 must be marked red because there is no bike."); + + // Verify buttons + Assert.IsTrue(viewModel.IsToggleVisible, "TINK and Konrad are activated in settings."); + Assert.AreEqual(Color.Gray, viewModel.TinkColor, "TINK bikes are hidden."); + + Assert.AreEqual(Color.Red, viewModel.KonradColor, "Konrad bikes are shown."); + + Assert.IsTrue( + new[] { "Updating...", "" }.Contains(viewModel.StatusInfoText), + "Text might be \"Updating...\" or empty depending on acivity of update thread."); + } + finally + { + await viewModel.OnDisappearing(); + } + } + + + + [Test] + public async Task TestConstruct_KonradOnly() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "Konrad", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.Off }, { "Konrad", FilterState.On } }), + new Uri("https://tinkwwp.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173)); // Current app version. Must be larger or equal 3.0.173 to + + var viewService = MockRepository.GenerateStub(); + var navigationService = MockRepository.GenerateStub(); + + var viewModel = new MapPageViewModel( + tinkApp, + new PermissionsMock(), + NSubstitute.Substitute.For(), + NSubstitute.Substitute.For(), + (mapspan) => { }, + viewService, + navigationService); + + try + { + await viewModel.OnAppearing(); // Is called when page shows. + + Assert.IsNull(viewModel.Exception); + + // Verify pins on map + Assert.AreEqual(2, viewModel.Pins.Count); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "31").Icon.Id.Contains("Green"), + "Station 5 must be marked green because there is are bike."); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "14").Icon.Id.Contains("Red"), + "Station 14 must be marked red because there is no bike."); + + // Verify buttons + Assert.IsFalse(viewModel.IsToggleVisible, "TINK and Konrad is deactivated from settings."); + + Assert.IsTrue( + new[] { "Updating...", "" }.Contains(viewModel.StatusInfoText), + "Text might be \"Updating...\" or empty depending on acivity of update thread."); + } + finally + { + await viewModel.OnDisappearing(); + } + } + + [Test] + public async Task TestConstruct_TinkOnly() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://tinkwwp.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173)); + + var viewService = MockRepository.GenerateStub(); + var navigationService = MockRepository.GenerateStub(); + + var viewModel = new MapPageViewModel( + tinkApp, + new PermissionsMock(), + NSubstitute.Substitute.For(), + NSubstitute.Substitute.For(), + (mapspan) => { }, + viewService, + navigationService); + + try + { + await viewModel.OnAppearing(); // Is called when page shows. + + Assert.IsNull(viewModel.Exception); + + // Verify pins on map + Assert.AreEqual(8, viewModel.Pins.Count); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "4").Icon.Id.Contains("Green"), + "Station 4 must be marked green because there is are bike."); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "31").Icon.Id.Contains("Red"), + "Station 31 must be marked red because there is no bike."); + + // Verify buttons + Assert.IsFalse(viewModel.IsToggleVisible, "TINK and Konrad is deactivated from settings."); + + Assert.IsTrue( + new[] { "Updating...", "" }.Contains(viewModel.StatusInfoText), + "Text might be \"Updating...\" or empty depending on acivity of update thread."); + } + finally + { + await viewModel.OnDisappearing(); + } + } + + [Test] + public async Task TestConstruct_Offline() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { FilterHelper.FILTERKONRAD, FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://tinkwwp.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => false, + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173)); // Current app version. Must be larger or equal 3.0.173 to + + var viewService = MockRepository.GenerateStub(); + var navigationService = MockRepository.GenerateStub(); + + var viewModel = new MapPageViewModel( + tinkApp, + new PermissionsMock(), + NSubstitute.Substitute.For(), + NSubstitute.Substitute.For(), + (mapspan) => { }, + viewService, + navigationService); + + try + { + await viewModel.OnAppearing(); // Is called when page shows. + + Assert.IsNull(viewModel.Exception); + + // Verify pins on map + Assert.AreEqual(8, viewModel.Pins.Count); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "4").Icon.Id.Contains("Green"), + "Station 4 must be marked green because there is are bike."); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "31").Icon.Id.Contains("Red"), + "Station 31 must be marked red because there is no bike."); + + // Verify buttons + Assert.IsTrue(viewModel.IsToggleVisible, "TINK and Konrad are activated in settings."); + Assert.AreEqual(Color.Blue, viewModel.TinkColor, "TINK bikes are hidden."); + + Assert.AreEqual(Color.Gray, viewModel.KonradColor, "Konrad bikes are shown."); + + + Assert.AreEqual("Offline.", viewModel.StatusInfoText); + } + finally + { + await viewModel.OnDisappearing(); + } + } + + [Test] + public async Task TestConstruct_WebConnectCommunicationError() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { FilterHelper.FILTERKONRAD, FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://tinkwwp.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + true /* IsReportLevelVerbose */, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new TINK.Model.Connector.Connector( + uri, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie, + mail, + server: new CopriProviderHttps( + uri, + TinkApp.MerchantId, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie: sessionCookie, + cacheServer: new CopriCallsCacheMemory(sessionCookie: sessionCookie), + httpsServer: new ExceptionServer((msg) => new WebConnectFailureException(msg, new Exception("Source expection."))))), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => false, + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173)); // Current app version. Must be larger or equal 3.0.173 to + + var viewService = MockRepository.GenerateStub(); + var navigationService = MockRepository.GenerateStub(); + + var viewModel = new MapPageViewModel( + tinkApp, + new PermissionsMock(), + NSubstitute.Substitute.For(), + NSubstitute.Substitute.For(), + (mapspan) => { }, + viewService, + navigationService); + + try + { + await viewModel.OnAppearing(); // Is called when page shows. + + Assert.AreEqual( + "Simulated error thrown at GetStationsAsync.", + viewModel.Exception.Message); + + // Verify pins on map + Assert.AreEqual(8, viewModel.Pins.Count); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "4").Icon.Id.Contains("Green"), + "Station 4 must be marked green because there is are bike."); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "31").Icon.Id.Contains("Red"), + "Station 31 must be marked red because there is no bike."); + + // Verify buttons + Assert.IsTrue(viewModel.IsToggleVisible, "TINK and Konrad are activated in settings."); + Assert.AreEqual(Color.Blue, viewModel.TinkColor, "TINK bikes are hidden."); + + Assert.AreEqual(Color.Gray, viewModel.KonradColor, "Konrad bikes are shown."); + + Assert.AreEqual("Connection interrupted, server unreachable.", viewModel.StatusInfoText); + } + finally + { + await viewModel.OnDisappearing(); + } + } + + + [Test] + public async Task TestConstruct_GeneralPurposeCommunicationError() + { + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { FilterHelper.FILTERKONRAD, FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://tinkwwp.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + true /* IsReportLevelVerbose */, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new TINK.Model.Connector.Connector( + uri, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie, + mail, + server: new CopriProviderHttps( + uri, + TinkApp.MerchantId, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie: sessionCookie, + cacheServer: new CopriCallsCacheMemory(sessionCookie: sessionCookie), + httpsServer: new ExceptionServer((msg) => new Exception(msg)))), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => false, + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173)); // Current app version. Must be larger or equal 3.0.173 to + + + var viewService = MockRepository.GenerateStub(); + var navigationService = MockRepository.GenerateStub(); + + var viewModel = new MapPageViewModel( + tinkApp, + new PermissionsMock(), + NSubstitute.Substitute.For(), + NSubstitute.Substitute.For(), + (mapspan) => { }, + viewService, + navigationService); + + try + { + await viewModel.OnAppearing(); // Is called when page shows. + + Assert.AreEqual( + "Simulated error thrown at GetStationsAsync.", + viewModel.Exception.Message); + + // Verify pins on map + Assert.AreEqual(8, viewModel.Pins.Count); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "4").Icon.Id.Contains("Green"), + "Station 4 must be marked green because there is are bike."); + Assert.IsTrue( + viewModel.Pins.FirstOrDefault(pin => pin.Tag.ToString() == "31").Icon.Id.Contains("Red"), + "Station 31 must be marked red because there is no bike."); + + // Verify buttons + Assert.IsTrue(viewModel.IsToggleVisible, "TINK and Konrad are activated in settings."); + Assert.AreEqual(Color.Blue , viewModel.TinkColor, "TINK bikes are hidden."); + + Assert.AreEqual(Color.Gray, viewModel.KonradColor, "Konrad bikes are shown."); + + Assert.AreEqual("Connection interrupted.", viewModel.StatusInfoText); + } + finally + { + await viewModel.OnDisappearing(); + } + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Settings/TestFilterCollectionMutable.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Settings/TestFilterCollectionMutable.cs new file mode 100644 index 0000000..ccf30e6 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Settings/TestFilterCollectionMutable.cs @@ -0,0 +1,110 @@ +using NUnit.Framework; +using System.Collections.Generic; +using TINK.Model; +using TINK.ViewModel.Settings; + + +namespace UITest.Fixtures.ObjectTests.ViewModel.Settings +{ + + [TestFixture] + public class TestFilterCollectionMutable + { + [Test] + public void TestConstruct_NoConradAccount() + { + var l_oColl = new SettingsBikeFilterViewModel( + new GroupFilterSettings(new Dictionary { + {"TINK", FilterState.On }, + {"Konrad", FilterState.On} + }), + new List { "TINK" }); + + Assert.AreEqual("TINK", l_oColl[0].Key); + Assert.IsTrue(l_oColl[0].IsActivated); + Assert.IsTrue(l_oColl[0].IsEnabled); + + Assert.AreEqual("Konrad", l_oColl[1].Key); + Assert.IsFalse(l_oColl[1].IsActivated, "Konrad must be off if user is not part of group."); + Assert.IsFalse(l_oColl[1].IsEnabled, "Konrad must be disabled if user is not part of group."); + + Assert.AreEqual(FilterState.On, l_oColl.FilterCollection["TINK"]); + Assert.AreEqual(FilterState.On, l_oColl.FilterCollection["Konrad"], "Filter state must be preserved."); + } + + [Test] + public void TestConstruct_ConradAccount() + { + var l_oColl = new SettingsBikeFilterViewModel( + new GroupFilterSettings(new Dictionary { + {"TINK", FilterState.On }, + {"Konrad", FilterState.On} + }), + new List { "TINK", "Konrad" }); + + Assert.AreEqual("TINK", l_oColl[0].Key); + Assert.IsTrue(l_oColl[0].IsActivated); + Assert.IsTrue(l_oColl[0].IsEnabled); + + Assert.AreEqual("Konrad", l_oColl[1].Key); + Assert.IsTrue(l_oColl[1].IsActivated); + Assert.IsTrue(l_oColl[1].IsEnabled); + + Assert.AreEqual(FilterState.On, l_oColl.FilterCollection["TINK"]); + Assert.AreEqual(FilterState.On, l_oColl.FilterCollection["Konrad"], "Filter state must be preserved."); + } + + [Test] + public void TestConstruct_TurnOff() + { + var l_oColl = new SettingsBikeFilterViewModel( + new GroupFilterSettings(new Dictionary { + {"TINK", FilterState.On }, + {"Konrad", FilterState.On} + }), + new List { "TINK", "Konrad" }); + + // Check prerequisites. + Assert.AreEqual("TINK", l_oColl[0].Key); + Assert.IsTrue(l_oColl[0].IsActivated); + Assert.IsTrue(l_oColl[0].IsEnabled); + + Assert.AreEqual("Konrad", l_oColl[1].Key); + Assert.IsTrue(l_oColl[1].IsActivated); + Assert.IsTrue(l_oColl[1].IsEnabled); + + Assert.AreEqual(FilterState.On, l_oColl.FilterCollection["TINK"]); + Assert.AreEqual(FilterState.On, l_oColl.FilterCollection["Konrad"], "Filter state must be preserved."); + + // Turn filter konrad off. + l_oColl[1].IsActivated = false; + + // Verify changes. + Assert.AreEqual(FilterState.On, l_oColl.FilterCollection["TINK"]); + Assert.AreEqual(FilterState.Off, l_oColl.FilterCollection["Konrad"], "Filter state must be preserved."); + } + + [Test] + public void TestConstruct_NoUserLoggedIn() + { + var l_oColl = new SettingsBikeFilterViewModel( + new GroupFilterSettings(new Dictionary { + {"TINK", FilterState.On }, + {"Konrad", FilterState.On} + }), + null); + + // Check prerequisites. + Assert.AreEqual("TINK", l_oColl[0].Key); + Assert.IsTrue(l_oColl[0].IsActivated); + Assert.IsTrue(l_oColl[0].IsEnabled); + + Assert.AreEqual("Konrad", l_oColl[1].Key); + Assert.IsTrue(l_oColl[1].IsActivated); + Assert.IsTrue(l_oColl[1].IsEnabled); + + Assert.AreEqual(FilterState.On, l_oColl.FilterCollection["TINK"]); + Assert.AreEqual(FilterState.On, l_oColl.FilterCollection["Konrad"], "Filter state must be preserved."); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeAtStationInUseStateInfoProvider.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeAtStationInUseStateInfoProvider.cs new file mode 100644 index 0000000..321d585 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeAtStationInUseStateInfoProvider.cs @@ -0,0 +1,27 @@ +using NUnit.Framework; +using System; +using TINK.ViewModel; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel +{ + [TestFixture] + public class TestBikeAtStationInUseStateInfoProvider + { + [Test] + public void TestGetReservedInfo() + { + Assert.That(new BikeAtStationInUseStateInfoProvider().GetReservedInfo(null), Is.EqualTo("Max. reservation time of 15 minutes expired.")); + Assert.That(new BikeAtStationInUseStateInfoProvider().GetReservedInfo(null, code: "Code123"), Is.Not.Null); + Assert.That(new BikeAtStationInUseStateInfoProvider().GetReservedInfo(TimeSpan.FromSeconds(3), code: "Code123"), Is.Not.Null); + Assert.That(new BikeAtStationInUseStateInfoProvider().GetReservedInfo(TimeSpan.FromSeconds(60)), Is.EqualTo("Still 1 minutes reserved.")); + } + + [Test] + public void Test() + { + Assert.That(new BikeAtStationInUseStateInfoProvider().GetBookedInfo(null), Is.EqualTo("Bike is rented.")); + Assert.That(new BikeAtStationInUseStateInfoProvider().GetBookedInfo(DateTime.Parse("2020-12-19 0:22"), code: "Code123"), Is.Not.Null); + Assert.That(new BikeAtStationInUseStateInfoProvider().GetBookedInfo(DateTime.Parse("2020-12-19 0:22"), code: "Code123"), Is.EqualTo("Code Code123, rented since 19. December 00:22.")); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeAtStationViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeAtStationViewModel.cs new file mode 100644 index 0000000..20cde99 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeAtStationViewModel.cs @@ -0,0 +1,223 @@ +using NUnit.Framework; +using System; +using TINK.Model.Bike; +using TINK.Model.User; +using TINK.Model.User.Account; +using TINK.ViewModel; +using TestTINKLib.Model.User.Account; + +using Xamarin.Forms; +using TINK.Model.State; +using Rhino.Mocks; +using System.Collections.Generic; + +using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable; +using TINK.ViewModel.Bikes; +using TINK.Model.Device; + +namespace UITest.Fixtures.ViewModel +{ + + [TestFixture] + public class TestBikeAtStationViewModel + { + private class BikeInfoMutable : TINK.Model.Bike.BC.BikeInfoMutable + { + public BikeInfoMutable( + string p_iId, + bool p_bIsDemo = false, + IEnumerable p_oGroup = null, + WheelType? p_eWheelType = null, + TypeOfBike? p_eTypeOfBike = null, + string description = null, + string p_strCurrentStationName = null, + Uri operatorUri = null, + Func p_oDateTimeProvider = null, + IStateInfo stateInfo = null) : base(p_iId, p_bIsDemo, p_oGroup, p_eWheelType, p_eTypeOfBike, description, p_strCurrentStationName, operatorUri, null, p_oDateTimeProvider, stateInfo) + { + } + } + /// + /// Tests base class functionaltiy by using child. + /// + [Test] + public void TestStateText_NotLoggedIn() + { + var l_oBike = new BikeInfoMutable("2", false, new List { "TINK" }, WheelType.Two, TypeOfBike.Cargo); + + var l_oStoreMock = new StoreMock(); // Account without user name, password and cookie + + var l_oUser = new User( + l_oStoreMock, + l_oStoreMock.Load().Result, + "123456789"); // Device identifier + + // Verify prerequisites + Assert.AreEqual(InUseStateEnum.Disposable, l_oBike.State.Value); + Assert.IsFalse(l_oUser.IsLoggedIn); + + // Verify view model. + var l_oViewModel = new TINK.ViewModel.Bikes.Bike.BC.BikeViewModel( + null, + null, + null, + null, + NSubstitute.Substitute.For(), + null, + l_oBike, + l_oUser, + new MyBikeInUseStateInfoProvider(), + MockRepository.GenerateStub()); + + Assert.AreEqual("2", l_oViewModel.Name); + Assert.AreEqual("", l_oViewModel.DisplayId); + Assert.AreEqual("2", l_oViewModel.Id); + Assert.AreEqual("Available.", l_oViewModel.StateText); + Assert.AreEqual(Color.Default, l_oViewModel.StateColor); + } + + /// + /// Tests base class functionaltiy by using child. + /// + [Test] + public void TestStateText_LoggedIn_Reserved() + { + var l_oViewModel = TestBikeViewModel.TestStateText_LoggedIn_Reserved( + (bike, user) => new TINK.ViewModel.Bikes.Bike.BC.BikeViewModel( + null, + null, + null, + null, + NSubstitute.Substitute.For(), + null, + bike, + user, + new BikeAtStationInUseStateInfoProvider(), + MockRepository.GenerateStub())); + + Assert.AreEqual("Still 15 minutes reserved.", l_oViewModel.StateText); + } + + /// + /// Tests base class functionaltiy by using child. + /// + [Test] + public void TestStateText_LoggedIn_ReservedWithCopriConnect() + { + var l_oViewModel = TestBikeViewModel.TestStateText_LoggedIn_ReservedWithCopriConnect( + (bike, user) => new TINK.ViewModel.Bikes.Bike.BC.BikeViewModel( + null, + null, + null, + null, + NSubstitute.Substitute.For(), + null, + bike, + user, + new BikeAtStationInUseStateInfoProvider(), + MockRepository.GenerateStub())); + + Assert.AreEqual("Code 4asdfA, still 7 minutes reserved.", l_oViewModel.StateText); + } + + /// + /// Tests base class functionaltiy by using child. + /// + [Test] + public void TestStateText_LoggedIn_Booked() + { + var l_oViewModel = TestBikeViewModel.TestStateText_LoggedIn_Booked( + (bike, user) => new TINK.ViewModel.Bikes.Bike.BC.BikeViewModel( + null, + null, + null, + null, + NSubstitute.Substitute.For(), + null, + bike, + user, + new BikeAtStationInUseStateInfoProvider(), + MockRepository.GenerateStub())); + + Assert.AreEqual( + $"Code 4asdfA, rented since {new DateTime(2018, 10, 24, 21, 49, 00).ToString("dd. MMMM HH:mm")}.", + l_oViewModel.StateText); + } + + /// + /// Tests base class functionaltiy by using child. + /// + [Test] + public void TestStateText_LoggedIn_ReservedBySomeoneElse() + { + var l_oBike = new BikeInfoMutable("2", false, new List { "TINK" }, WheelType.Two, TypeOfBike.Cargo, "Test description", "3"); + + l_oBike.State.Load( + InUseStateEnum.Reserved, + new DateTime(2017, 10, 24, 21, 49, 3), + "ragu@gnu-systems.de", + "4asdfA"); + + var l_oStoreMock = new StoreMock(new Account("john@long", "123456789" /* password */, "987654321" /* session cookie */, new List { "TINK" })); + + var l_oViewModel = new TINK.ViewModel.Bikes.Bike.BC.BikeViewModel( + null, + null, + null, + null, + NSubstitute.Substitute.For(), + null, + l_oBike, + new User( + l_oStoreMock, + l_oStoreMock.Load().Result, + "123456789"), // Device id + new BikeAtStationInUseStateInfoProvider(), + MockRepository.GenerateStub()); + + Assert.AreEqual("Test description", l_oViewModel.Name); + Assert.AreEqual("2", l_oViewModel.DisplayId); + Assert.AreEqual("2", l_oViewModel.Id); + Assert.AreEqual("Fahrrad bereits reserviert durch anderen Nutzer.", l_oViewModel.StateText); + Assert.AreEqual(Color.Red, l_oViewModel.StateColor); + } + + /// + /// Tests base class functionaltiy by using child. + /// + [Test] + public void TestStateText_LoggedIn_BookedBySomeoneElse() + { + var l_oBike = new BikeInfoMutable("2", false, new List { "TINK" }, WheelType.Two, TypeOfBike.Cargo, "Test description", "3"); + + l_oBike.State.Load( + InUseStateEnum.Booked, + new DateTime(2017, 10, 24, 21, 49, 3), + "ragu@gnu-systems.de", + "4asdfA"); + + var l_oStoreMock = new StoreMock(new Account("john@long", "123456789" /* password */, "987654321" /* session cookie */, new List { "TINK" })); + + var l_oViewModel = new TINK.ViewModel.Bikes.Bike.BC.BikeViewModel( + null, + null, + null, + null, + NSubstitute.Substitute.For(), + null, + l_oBike, + new User( + l_oStoreMock, + l_oStoreMock.Load().Result, + "123456789"), + new BikeAtStationInUseStateInfoProvider(), + MockRepository.GenerateStub()); + + Assert.AreEqual("Test description", l_oViewModel.Name); + Assert.AreEqual("2", l_oViewModel.DisplayId); + Assert.AreEqual("2", l_oViewModel.Id); + Assert.AreEqual("Fahrrad bereits gebucht durch anderen Nutzer.", l_oViewModel.StateText); + Assert.AreEqual(Color.Red, l_oViewModel.StateColor); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeViewModel.cs new file mode 100644 index 0000000..74008ec --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeViewModel.cs @@ -0,0 +1,144 @@ +using NUnit.Framework; +using System; +using TINK.Model.Bike; +using TINK.Model.User; +using TINK.Model.User.Account; +using TINK.ViewModel; +using TestTINKLib.Model.User.Account; +using static TINK.Repository.CopriCallsMemory; +using TINK.Model.State; +using System.Collections.Generic; +using TINK.Repository; +using TINK.ViewModel.Bikes.Bike; + +namespace UITest.Fixtures.ViewModel +{ + public class TestBikeViewModel + { + private class BikeInfoMutable : TINK.Model.Bike.BC.BikeInfoMutable + { + public BikeInfoMutable( + string p_iId, + bool p_bIsDemo = false, + IEnumerable p_oGroup = null, + WheelType? p_eWheelType = null, + TypeOfBike? p_eTypeOfBike = null, + string description = null, + string p_strCurrentStationName = null, + Uri operatorUri = null, + Func p_oDateTimeProvider = null, + IStateInfo stateInfo = null) : base(p_iId, p_bIsDemo, p_oGroup, p_eWheelType, p_eTypeOfBike, description, p_strCurrentStationName, operatorUri, null, p_oDateTimeProvider, stateInfo) + { + } + } + /// + /// Tests base class functionaltiy by using child. + /// + public static BikeViewModelBase TestStateText_LoggedIn_Reserved(Func p_oFactory) + { + var l_oBike = new BikeInfoMutable( + "2", + false, + new List { "TINK" }, + WheelType.Trike, + TypeOfBike.Cargo, + "Test description", + "3", + null, + () => new DateTime(1980, 1, 1)); // Now time stamp + + // Update state from Copri. + l_oBike.State.Load( + InUseStateEnum.Reserved, // Copri acknowledges state reserved. + new DateTime(1980, 1, 1), // Date when bike was booked. + "ragu@gnu-systems.de"); // Owner from Copri. + + var l_oStoreMock = new StoreMock(new Account("ragu@gnu-systems.de", "123456789" /* password */, "987654321" /* session cookie */, new List { "TINK" })); + var l_oUser = new User( + l_oStoreMock, + l_oStoreMock.Load().Result, + "123456789"); + + // Verify prerequisites + Assert.AreEqual(InUseStateEnum.Reserved, l_oBike.State.Value); + Assert.IsTrue(l_oUser.IsLoggedIn); + Assert.AreEqual(l_oBike.State.MailAddress, l_oUser.Mail); + + // Do not update from Copri + var l_oViewModel = p_oFactory(l_oBike, l_oUser); + + return l_oViewModel; + } + + /// + /// Tests base class functionaltiy by using child. + /// + public static BikeViewModelBase TestStateText_LoggedIn_ReservedWithCopriConnect(Func p_oFactory) + { + var l_oBike = new BikeInfoMutable( + "2", + false, + new List { "TINK" }, + WheelType.Trike, + TypeOfBike.Cargo, + "Test description", + "3", + null, + () => (new DateTime(1980, 1, 1)).Add(new TimeSpan(0, 8, 0))); + + // Update state from Copri. + l_oBike.State.Load( + InUseStateEnum.Reserved, // Copri acknowledges state reserved. + new DateTime(1980, 1, 1), + "ragu@gnu-systems.de", // Owner from Copri. + "4asdfA"); // Reservation code from Copri + + var l_oStoreMock = new StoreMock(new Account("ragu@gnu-systems.de", "123456789" /* password */, "987654321" /* session cookie */, new List { "TINK" })); + var l_oUser = new User( + l_oStoreMock, // Mocks account store functionality. + l_oStoreMock.Load().Result, + "123456789"); + + // Verify prerequisites + Assert.AreEqual(InUseStateEnum.Reserved, l_oBike.State.Value); + Assert.IsTrue(l_oUser.IsLoggedIn); + Assert.AreEqual(l_oBike.State.MailAddress, l_oUser.Mail); + + var l_oViewModel = p_oFactory(l_oBike, l_oUser); // Bikes collection mock. + + return l_oViewModel; + } + + /// + /// Tests base class functionaltiy by using child. + /// + public static BikeViewModelBase TestStateText_LoggedIn_Booked(Func p_oFactory) + { + var l_oBike = new BikeInfoMutable("2", false, new List { "TINK" }, WheelType.Two, TypeOfBike.Cargo, "Test description", "3"); + + // Update from Copri. + l_oBike.State.Load( + InUseStateEnum.Booked, + new DateTime(2017, 10, 24, 21, 49, 3), + "ragu@gnu-systems.de", + "4asdfA"); + + var l_oCopriServer = new CopriCallsMemory(SampleSets.Set1, 1); + + var l_oStoreMock = new StoreMock(new Account("ragu@gnu-systems.de", "123456789" /* password */, "987654321" /* session cookie */, new List { "TINK" })); + var l_oUser = new User( + l_oStoreMock, + l_oStoreMock.Load().Result, + "123456789"); // Device id + + // Verify prerequisites + Assert.AreEqual(InUseStateEnum.Booked, l_oBike.State.Value); + Assert.IsTrue(l_oUser.IsLoggedIn); + Assert.AreEqual(l_oBike.State.MailAddress, l_oUser.Mail); + + var l_oViewModel = p_oFactory(l_oBike, l_oUser); + + return l_oViewModel; + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeViewModelFactory.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeViewModelFactory.cs new file mode 100644 index 0000000..f57d4cf --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikeViewModelFactory.cs @@ -0,0 +1,52 @@ +using NUnit.Framework; +using Rhino.Mocks; +using System; +using TestTINKLib.Mocks.Services; +using TINK.Model.Device; +using TINK.Model.User; +using TINK.ViewModel; +using TINK.ViewModel.Bikes; +using TINK.ViewModel.Bikes.Bike; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel +{ + [TestFixture] + public class TestBikeViewModelFactory + { + [Test] + public void TestCreate() + { + Assert.AreEqual( + typeof(TINK.ViewModel.Bikes.Bike.BC.BikeViewModel), + BikeViewModelFactory.Create( + () => false, // Is connected delegate, + (isconnected) => null, // connectorFactory + new GeolocationMock(), // geolocation + new LocksServiceMock(), // lock service + (index) => { }, // bikeRemoveDelegate + null, // viewUpdateManager + NSubstitute.Substitute.For(), + null, // viewService + new TINK.Model.Bike.BC.BikeInfoMutable(new TINK.Model.Bike.BluetoothLock.BikeInfo("42", 5200544, new Guid("00000000-0000-0000-0000-000000000001"), "42")), + MockRepository.GenerateStub(), // user + MockRepository.GenerateStub(), + MockRepository.GenerateStub()).GetType()); // stateInfoProvider + + Assert.AreEqual( + typeof(TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel), + BikeViewModelFactory.Create( + () => false, // Is connected delegate, + (isconnected) => null, // connectorFactory + new GeolocationMock(), // geolocation + new LocksServiceMock(), // lock service + (index) => { }, // bikeRemoveDelegate + null, // viewUpdateManager + NSubstitute.Substitute.For(), + null, // viewService + new TINK.Model.Bike.BluetoothLock.BikeInfoMutable(new TINK.Model.Bike.BluetoothLock.BikeInfo("42", 5200544, new Guid("00000000-0000-0000-0000-000000000001"), "42")), + MockRepository.GenerateStub(), // user + MockRepository.GenerateStub(), + MockRepository.GenerateStub()).GetType()); // stateInfoProvider + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikesAtStationPageViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikesAtStationPageViewModel.cs new file mode 100644 index 0000000..841b7a7 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestBikesAtStationPageViewModel.cs @@ -0,0 +1,1152 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using TestTINKLib.Mocks.Connector; +using TestTINKLib.Mocks.Device; +using TestTINKLib.Mocks.Services; +using TestTINKLib.Model.User.Account; +using TINK.Model; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.View; +using TINK.Model.Services.CopriApi; +using TINK.Repository; + +using static TINK.Repository.CopriCallsMemory; +using TINK.ViewModel.Map; +using TINK.ViewModel.Settings; +using Plugin.Permissions.Abstractions; +using Plugin.BLE.Abstractions.Contracts; +using TINK.Services.BluetoothLock; +using NSubstitute; +using TINK.ViewModel.BikesAtStation; +using Xamarin.Forms; +using TINK.Services.BluetoothLock.Tdo; +using Plugin.Permissions; +using TINK.Model.Services.Geolocation; +using NSubstitute.ExceptionExtensions; +using TINK.Services; +using TINK.Model.Device; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel +{ + [TestFixture] + public class TestBikesAtStationPageViewModel + { + [Test] + public async Task TestConstruct_Droid_NotLoggedIn() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("", null, null, new List ())), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.Android, + new TINK.Model.Station.Station("103", new List(), null), // Station 103 + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + Assert.IsEmpty(bikesAtStation.StatusInfoText); + + // Verify list of bikes + Assert.AreEqual(2, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1315 = bikesAtStation.FirstOrDefault(x => x.Id == "1315") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1543 = bikesAtStation.FirstOrDefault(x => x.Id == "1543") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + + Assert.AreEqual("Available.", bike1315.StateText); + Assert.AreEqual("Available.", bike1543.StateText); + Assert.AreEqual("NotLoggedIn", bike1315.LockitButtonText); + Assert.AreEqual("NotLoggedIn", bike1543.LockitButtonText); + + // Login hint/ no bikes frame + Assert.IsTrue(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestConstruct_Droid_NoBikes() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.Android, + new TINK.Model.Station.Station("102", new List(), null), // Station 102 + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + Assert.IsEmpty(bikesAtStation.StatusInfoText); + + // Verify list of bikes + Assert.AreEqual(0, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsFalse(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + // Login hint/ no bikes frame + Assert.IsFalse(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsTrue(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual("Momentan sind keine Fahrräder an dieser Station verfügbar.", bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestConstruct_Droid() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + // Fake location permissions to be set + permissions.CheckPermissionStatusAsync().Returns(Task.FromResult(PermissionStatus.Granted)); + geolocation.Active.IsGeolcationEnabled.Returns(true); // Fake gps to be on + bluetooth.State.Returns(BluetoothState.On); // Fake bluetooth to be on + + // Fake bluetooth answer for locks with id 2200545 and 2200537. + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()).Returns( + new List { + { new LockInfoTdo.Builder { Id = 2200545, State = LockitLockingState.Open }.Build() }, + { new LockInfoTdo.Builder { Id = 2200537, State = LockitLockingState.Closed }.Build() } + } + ); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, // Instance geolocation mocks both parts + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.Android, + new TINK.Model.Station.Station("103", new List(), null), // Station 103 + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + // Verify behaviour + Received.InOrder(() => + { + permissions.CheckPermissionStatusAsync(); + var glDummy = geolocation.Active.Received().IsGeolcationEnabled; + var btDummy = bluetooth.Received().State; + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()); + }); + + Assert.IsEmpty(bikesAtStation.StatusInfoText); + + // Verify list of bikes + Assert.AreEqual(4, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1545 = bikesAtStation.FirstOrDefault(x => x.Id == "1545") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1537 = bikesAtStation.FirstOrDefault(x => x.Id == "1537") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1315 = bikesAtStation.FirstOrDefault(x => x.Id == "1315") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1543 = bikesAtStation.FirstOrDefault(x => x.Id == "1543") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + + Assert.AreEqual("Rented since 06. November 17:53.", bike1545.StateText); + Assert.AreEqual("Rented since 12. October 08:38.", bike1537.StateText); + Assert.AreEqual("Available.", bike1315.StateText); + Assert.AreEqual("Available.", bike1543.StateText); + Assert.AreEqual("Close lock", bike1545.LockitButtonText); + Assert.AreEqual("Open lock & continue renting", bike1537.LockitButtonText); + Assert.AreEqual("DisposableDisconnected", bike1315.LockitButtonText); + Assert.AreEqual("DisposableDisconnected", bike1543.LockitButtonText); + + // Login hint/ no bikes frame + Assert.IsFalse(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestConstruct_Droid_NoPermissions_OpenSettings() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + // Fake location permissions not to be set + permissions.CheckPermissionStatusAsync().Returns(Task.FromResult(PermissionStatus.Denied)); + + // Fake anwser on question whether to open permissions dialog + viewService.DisplayAlert(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); + + // Fake bluetooth answer for locks with id 2200545 and 2200537. + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()).Returns( + new List { + { new LockInfoTdo.Builder { Id = 2200545, State = LockitLockingState.Open }.Build() }, + { new LockInfoTdo.Builder { Id = 2200537, State = LockitLockingState.Closed }.Build() } + } + ); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.Android, + new TINK.Model.Station.Station("103", new List(), null), // Station 103 + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + // Verify behaviour + Received.InOrder(() => + { + permissions.CheckPermissionStatusAsync(); + var glDummy = geolocation.Active.Received().IsGeolcationEnabled; + permissions.RequestPermissionAsync(); // Ask user from permissions. + viewService.DisplayAlert( + "Hint", + "Please allow location sharing so that bike lock/locks can be managed.\r\nOpen sharing dialog?", + "Yes", + "No"); + permissions.OpenAppSettings(); + }); + + Assert.IsEmpty(bikesAtStation.StatusInfoText); + + // Verify list of bikes + Assert.AreEqual(4, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1545 = bikesAtStation.FirstOrDefault(x => x.Id == "1545") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1537 = bikesAtStation.FirstOrDefault(x => x.Id == "1537") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1315 = bikesAtStation.FirstOrDefault(x => x.Id == "1315") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1543 = bikesAtStation.FirstOrDefault(x => x.Id == "1543") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + + Assert.AreEqual("Rented since 06. November 17:53.", bike1545.StateText); + Assert.AreEqual("Rented since 12. October 08:38.", bike1537.StateText); + Assert.AreEqual("Available.", bike1315.StateText); + Assert.AreEqual("Available.", bike1543.StateText); + Assert.AreEqual("Search lock", bike1545.LockitButtonText); + Assert.AreEqual("Search lock", bike1537.LockitButtonText); + Assert.AreEqual("DisposableDisconnected", bike1315.LockitButtonText); + Assert.AreEqual("DisposableDisconnected", bike1543.LockitButtonText); + + // Login hint/ no bikes frame + Assert.IsFalse(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestConstruct_Droid_NoPermissions() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + // Fake location permissions not to be set + permissions.CheckPermissionStatusAsync().Returns(Task.FromResult(PermissionStatus.Denied)); + permissions.OpenAppSettings().Throws(); // Ensures that method is not called and fixture succeeds. + + // Fake anwser on question whether to open permissions dialog + viewService.DisplayAlert(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(false); + + // Fake bluetooth answer for locks with id 2200545 and 2200537. + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()).Returns( + new List { + { new LockInfoTdo.Builder { Id = 2200545, State = LockitLockingState.Open }.Build() }, + { new LockInfoTdo.Builder { Id = 2200537, State = LockitLockingState.Closed }.Build() } + } + ); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.Android, + new TINK.Model.Station.Station("103", new List(), null), // Station 103 + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + // Verify behaviour + Received.InOrder(() => + { + permissions.CheckPermissionStatusAsync(); + var glDummy = geolocation.Active.Received().IsGeolcationEnabled; + permissions.RequestPermissionAsync(); // Ask user from permissions. + viewService.DisplayAlert( + "Hint", + "Please allow location sharing so that bike lock/locks can be managed.\r\nOpen sharing dialog?", + "Yes", + "No"); + }); + + Assert.IsEmpty(bikesAtStation.StatusInfoText); + + // Verify list of bikes + Assert.AreEqual(4, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1545 = bikesAtStation.FirstOrDefault(x => x.Id == "1545") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1537 = bikesAtStation.FirstOrDefault(x => x.Id == "1537") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1315 = bikesAtStation.FirstOrDefault(x => x.Id == "1315") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1543 = bikesAtStation.FirstOrDefault(x => x.Id == "1543") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + + Assert.AreEqual("Rented since 06. November 17:53.", bike1545.StateText); + Assert.AreEqual("Rented since 12. October 08:38.", bike1537.StateText); + Assert.AreEqual("Available.", bike1315.StateText); + Assert.AreEqual("Available.", bike1543.StateText); + Assert.AreEqual("Search lock", bike1545.LockitButtonText); + Assert.AreEqual("Search lock", bike1537.LockitButtonText); + Assert.AreEqual("DisposableDisconnected", bike1315.LockitButtonText); + Assert.AreEqual("DisposableDisconnected", bike1543.LockitButtonText); + + // Login hint/ no bikes frame + Assert.IsFalse(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestConstruct_Droid_GeolocationOff() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + // Fake location permissions to be set + permissions.CheckPermissionStatusAsync().Returns(Task.FromResult(PermissionStatus.Granted)); + geolocation.Active.IsGeolcationEnabled.Returns(false); // Fake gps to be off + bluetooth.State.Returns(BluetoothState.On); // Fake bluetooth to be on + + // Fake bluetooth answer for locks with id 2200545 and 2200537. + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()).Returns( + new List { + { new LockInfoTdo.Builder { Id = 2200545, State = LockitLockingState.Open }.Build() }, + { new LockInfoTdo.Builder { Id = 2200537, State = LockitLockingState.Closed }.Build() } + } + ); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.Android, + new TINK.Model.Station.Station("103", new List(), null), // Station 103 + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + // Verify behaviour + Received.InOrder(() => + { + permissions.CheckPermissionStatusAsync(); + var glDummy = geolocation.Active.Received().IsGeolcationEnabled; + viewService.DisplayAlert( + "Hint", + "Please activate location so that bike lock can be found!", + "OK"); + }); + + Assert.IsEmpty(bikesAtStation.StatusInfoText); + + // Verify list of bikes + Assert.AreEqual(4, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1545 = bikesAtStation.FirstOrDefault(x => x.Id == "1545") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1537 = bikesAtStation.FirstOrDefault(x => x.Id == "1537") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1315 = bikesAtStation.FirstOrDefault(x => x.Id == "1315") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1543 = bikesAtStation.FirstOrDefault(x => x.Id == "1543") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + + Assert.AreEqual("Rented since 06. November 17:53.", bike1545.StateText); + Assert.AreEqual("Rented since 12. October 08:38.", bike1537.StateText); + Assert.AreEqual("Available.", bike1315.StateText); + Assert.AreEqual("Available.", bike1543.StateText); + Assert.AreEqual("Search lock", bike1545.LockitButtonText); + Assert.AreEqual("Search lock", bike1537.LockitButtonText); + Assert.AreEqual("DisposableDisconnected", bike1315.LockitButtonText); + Assert.AreEqual("DisposableDisconnected", bike1543.LockitButtonText); + + // Login hint/ no bikes frame + Assert.IsFalse(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestConstruct_Droid_BluetoothOff() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + // Fake location permissions to be set + permissions.CheckPermissionStatusAsync().Returns(Task.FromResult(PermissionStatus.Granted)); + geolocation.Active.IsGeolcationEnabled.Returns(true); // Fake gps to be on + bluetooth.State.Returns(BluetoothState.Off); // // Fake bluetooth to be off + + // Fake bluetooth answer for locks with id 2200545 and 2200537. + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()).Returns( + new List { + { new LockInfoTdo.Builder { Id = 2200545, State = LockitLockingState.Open }.Build() }, + { new LockInfoTdo.Builder { Id = 2200537, State = LockitLockingState.Closed }.Build() } + } + ); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.Android, + new TINK.Model.Station.Station("103", new List(), null), // Station 103 + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + // Verify behaviour + Received.InOrder(() => + { + permissions.CheckPermissionStatusAsync(); + var glDummy = geolocation.Active.Received().IsGeolcationEnabled; + var btDummy = bluetooth.Received().State; + viewService.DisplayAlert( + "Hint", + "Please enable Bluetooth to manage bike lock/locks.", + "OK"); + }); + + Assert.IsEmpty(bikesAtStation.StatusInfoText); + + // Verify list of bikes + Assert.AreEqual(4, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1545 = bikesAtStation.FirstOrDefault(x => x.Id == "1545") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1537 = bikesAtStation.FirstOrDefault(x => x.Id == "1537") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1315 = bikesAtStation.FirstOrDefault(x => x.Id == "1315") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1543 = bikesAtStation.FirstOrDefault(x => x.Id == "1543") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + + Assert.AreEqual("Rented since 06. November 17:53.", bike1545.StateText); + Assert.AreEqual("Rented since 12. October 08:38.", bike1537.StateText); + Assert.AreEqual("Available.", bike1315.StateText); + Assert.AreEqual("Available.", bike1543.StateText); + Assert.AreEqual("Search lock", bike1545.LockitButtonText); + Assert.AreEqual("Search lock", bike1537.LockitButtonText); + Assert.AreEqual("DisposableDisconnected", bike1315.LockitButtonText); + Assert.AreEqual("DisposableDisconnected", bike1543.LockitButtonText); + + // Login hint/ no bikes frame + Assert.IsFalse(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestConstructKonrad() + { + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + bluetooth.State.Returns(BluetoothState.On); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.Off }, { "Konrad", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000),true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.iOS, + new TINK.Model.Station.Station("31", new List(), null), + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + new GeolocationMock(), + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + Assert.IsTrue(new List { "Updating...", string.Empty }.Contains(bikesAtStation.StatusInfoText)); + + // Verify list of bikes + Assert.AreEqual(1, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "52").StateText); + + // Login hint/ no bikes frame + Assert.IsTrue(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + + + [Test] + public async Task TestConstructKonrad_NoBikes() + { + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + bluetooth.State.Returns(BluetoothState.On); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.Off }, { "Konrad", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://tinkwwp.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.iOS, + new TINK.Model.Station.Station("4", new List(), null), + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + new GeolocationMock(), + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + Assert.IsTrue(new List { "Updating...", string.Empty }.Contains(bikesAtStation.StatusInfoText)); + + // Verify list of bikes + Assert.AreEqual(0, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsFalse(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + // Login hint/ no bikes frame + Assert.IsFalse(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsTrue(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual("Momentan sind keine Fahrräder an dieser Station verfügbar.", bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestConstruct_LoggedIn() + { + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + bluetooth.State.Returns(BluetoothState.On); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: locksService.GetType().FullName), + new StoreMock(new TINK.Model.User.Account.Account( + LoginSessionCopriInfo.JavaministerHardwareNr1.Mail, + LoginSessionCopriInfo.JavaministerHardwareNr1.Pwd, + "4da3044c8657a04ba60e2eaa753bc51a", + new List { "TINK" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.iOS, + new TINK.Model.Station.Station("4", new List(), null), + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + new GeolocationMock(), + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + Assert.IsTrue(new List { "Updating...", string.Empty }.Contains(bikesAtStation.StatusInfoText)); + + // Verify list of bikes + Assert.AreEqual(4, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "5").StateText); + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "14").StateText); + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "26").StateText); + Assert.AreEqual("Code 2931, rented since 28. November 13:06.", bikesAtStation.FirstOrDefault(x => x.Id == "7").StateText); + + // Login hint/ no bikes frame + Assert.IsFalse(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestConstruct_Offline() + { + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + bluetooth.State.Returns(BluetoothState.On); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => false, // Not connected. + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.iOS, + new TINK.Model.Station.Station("4", new List(), null), + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + new GeolocationMock(), + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await bikesAtStation.OnAppearing(); + + Assert.AreEqual("Offline.", bikesAtStation.StatusInfoText); + + // Verify list of bikes + Assert.AreEqual(3, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "5").StateText); + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "14").StateText); + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "26").StateText); + + // Login hint/ no bikes frame + Assert.IsTrue(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestConstruct_WebConnectCommunicationError() + { + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + bluetooth.State.Returns(BluetoothState.On); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new TINK.Model.Connector.Connector( + uri, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie, + mail, + server: new CopriProviderHttps( + uri, + TinkApp.MerchantId, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie: sessionCookie, + cacheServer: new CopriCallsCacheMemory(sessionCookie: sessionCookie), + httpsServer: new ExceptionServer((msg) => new WebConnectFailureException(msg, new Exception("Source expection."))))), + Substitute.For>(), + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => false, // Not connected. + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.iOS, + new TINK.Model.Station.Station("4", new List(), null), + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + new GeolocationMock(), + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService) + { + IsReportLevelVerbose = true + }; + + await bikesAtStation.OnAppearing(); + + Assert.AreEqual("Connection interrupted, server unreachable.", bikesAtStation.StatusInfoText); + + // Verify list of bikes + Assert.AreEqual(3, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "5").StateText); + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "14").StateText); + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "26").StateText); + + // Login hint/ no bikes frame + Assert.IsTrue(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + + [Test] + public async Task TestTinkApp_GeneralPurposeError() + { + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + bluetooth.State.Returns(BluetoothState.On); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), + true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new TINK.Model.Connector.Connector( + uri, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie, + mail, + server: new CopriProviderHttps( + uri, + TinkApp.MerchantId, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie: sessionCookie, + cacheServer: new CopriCallsCacheMemory(sessionCookie: sessionCookie), + httpsServer: new ExceptionServer((msg) => new Exception(msg)))), + Substitute.For>(), + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => false, // Not connected. + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var bikesAtStation = new BikesAtStationPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.iOS, + new TINK.Model.Station.Station("4", new List(), null), + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + new GeolocationMock(), + locksService, + tinkApp.Polling, + (url) => { }, + (d, obj) => d(obj), + Substitute.For(), + viewService) + { + IsReportLevelVerbose = true + }; + + await bikesAtStation.OnAppearing(); + + Assert.AreEqual("Connection interrupted.", bikesAtStation.StatusInfoText); + + // Verify list of bikes + Assert.AreEqual(3, bikesAtStation.Count); + Assert.IsTrue(bikesAtStation.IsIdle); + Assert.IsTrue(bikesAtStation.IsBikesListVisible, "If there are any bikes, list must be visible."); + + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "5").StateText); + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "14").StateText); + Assert.AreEqual("Available.", bikesAtStation.FirstOrDefault(x => x.Id == "26").StateText); + + // Login hint/ no bikes frame + Assert.IsTrue(bikesAtStation.IsLoginRequiredHintVisible); + Assert.IsFalse(bikesAtStation.IsNoBikesAtStationVisible); + Assert.AreEqual(string.Empty, bikesAtStation.NoBikesAtStationText); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikeInUseStateInfoProvider.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikeInUseStateInfoProvider.cs new file mode 100644 index 0000000..472e20d --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikeInUseStateInfoProvider.cs @@ -0,0 +1,31 @@ +using NUnit.Framework; +using System; +using TINK.ViewModel; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel +{ + [TestFixture] + public class TestMyBikeInUseStateInfoProvider + { + [Test] + public void TestGetReservedInfo() + { + Assert.That(new MyBikeInUseStateInfoProvider().GetReservedInfo(null), Is.EqualTo("Max. reservation time of 15 minutes expired.")); + Assert.That(new MyBikeInUseStateInfoProvider().GetReservedInfo(null, code: "Code12"), Is.Not.Null); + Assert.That(new MyBikeInUseStateInfoProvider().GetReservedInfo(null, "12"), Is.EqualTo("Location 12, max. reservation time of 15 minutes expired.")); + Assert.That(new MyBikeInUseStateInfoProvider().GetReservedInfo(null, "12", "Code12"), Is.Not.Null); + Assert.That(new MyBikeInUseStateInfoProvider().GetReservedInfo(TimeSpan.FromSeconds(10)), Is.Not.Null); + Assert.That(new MyBikeInUseStateInfoProvider().GetReservedInfo(TimeSpan.FromSeconds(10), "123"), Is.EqualTo("Location Station 123, still 0 minutes reserved.")); + Assert.That(new MyBikeInUseStateInfoProvider().GetReservedInfo(TimeSpan.FromSeconds(10), "123", "code123"), Is.Not.Null); + } + + [Test] + public void TestGetBookedInfoInfo() + { + Assert.That(new MyBikeInUseStateInfoProvider().GetBookedInfo(null), Is.EqualTo("Bike is rented.")); + Assert.That(new MyBikeInUseStateInfoProvider().GetBookedInfo(DateTime.Now, "123", "Code123"), Is.Not.Null); + Assert.That(new MyBikeInUseStateInfoProvider().GetBookedInfo(DateTime.Now, code: "Code123"), Is.Not.Null); + Assert.That(new MyBikeInUseStateInfoProvider().GetBookedInfo(DateTime.Parse("2020-12-19 0:22"), "123"), Is.EqualTo("Rented since 19. December 00:22.")); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikePageViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikePageViewModel.cs new file mode 100644 index 0000000..b3a5307 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikePageViewModel.cs @@ -0,0 +1,85 @@ +using NUnit.Framework; +using Rhino.Mocks; +using System; +using TINK.Model.Device; +using TINK.ViewModel; +using TINK.ViewModel.Bikes; + +namespace UITest.Fixtures.ViewModel +{ + + [TestFixture] + public class TestMyBikesPageViewModel + { + /// + /// Tests base class functionaltiy by using child. + /// + [Test] + public void TestStateText_LoggedIn_Reserved() + { + var l_oViewModel = TestBikeViewModel.TestStateText_LoggedIn_Reserved( + (bike, user) => new TINK.ViewModel.Bikes.Bike.BC.BikeViewModel( + null, + null, + null, + null, + NSubstitute.Substitute.For(), + null, + bike, + user, + new MyBikeInUseStateInfoProvider(), + MockRepository.GenerateStub())); + + + Assert.AreEqual("Location Station 3, still 15 minutes reserved.", l_oViewModel.StateText); + } + + /// + /// Tests base class functionaltiy by using child. + /// + [Test] + public void TestStateText_LoggedIn_ReservedWithCopriConnect() + { + var l_oViewModel = TestBikeViewModel.TestStateText_LoggedIn_ReservedWithCopriConnect( + (bike, user) => new TINK.ViewModel.Bikes.Bike.BC.BikeViewModel( + null, + null, + null, + null, + NSubstitute.Substitute.For(), + null, + bike, + user, + new MyBikeInUseStateInfoProvider(), + MockRepository.GenerateStub())); + + Assert.AreEqual("Code 4asdfA, location Station 3, still 7 minutes reserved.", l_oViewModel.StateText); + } + + + /// + /// Tests base class functionaltiy by using child. + /// + /// + [Test] + public void TestStateText_LoggedIn_Booked() + { + var l_oViewModel = TestBikeViewModel.TestStateText_LoggedIn_Booked( + (bike, user) => new TINK.ViewModel.Bikes.Bike.BC.BikeViewModel( + null, + null, + null, + null, + NSubstitute.Substitute.For(), + null, + bike, + user, + new MyBikeInUseStateInfoProvider(), + MockRepository.GenerateStub())); + + Assert.AreEqual( + $"Code 4asdfA, location Station 3, rented since {new DateTime(2018, 10, 24, 21, 49, 00).ToString("dd. MMMM HH:mm")}.", + l_oViewModel.StateText); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikesPageViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikesPageViewModel.cs new file mode 100644 index 0000000..08ac317 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikesPageViewModel.cs @@ -0,0 +1,788 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using TestTINKLib.Mocks.Connector; +using TestTINKLib.Mocks.Device; +using TestTINKLib.Mocks.Services; +using TestTINKLib.Model.User.Account; +using TINK.Model; +using TINK.Model.Connector; +using TINK.Repository.Exception; +using TINK.View; +using TINK.Model.Services.CopriApi; +using TINK.Repository; +using static TINK.Repository.CopriCallsMemory; +using TINK.ViewModel.Map; +using TINK.ViewModel.Settings; +using Plugin.Permissions.Abstractions; +using Plugin.BLE.Abstractions.Contracts; +using TINK.Services.BluetoothLock; +using NSubstitute; +using TINK.Services.BluetoothLock.Tdo; +using TINK.ViewModel.MyBikes; +using Plugin.Permissions; +using Xamarin.Forms; +using TINK.Model.Services.Geolocation; +using NSubstitute.ExceptionExtensions; +using TINK.Services; +using TINK.Model.Device; + +namespace TestTINKLib.Fixtures.ObjectTests.ViewModel +{ + [TestFixture] + public class TestMyBikesPageViewModel + { + [Test] + public async Task TestConstruct_Droid() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + // Fake location permissions to be set + permissions.CheckPermissionStatusAsync().Returns(Task.FromResult(PermissionStatus.Granted)); + geolocation.Active.IsGeolcationEnabled.Returns(true); // Fake gps to be on + bluetooth.State.Returns(BluetoothState.On); // Fake bluetooth to be on + + // Fake bluetooth answer for locks with id 2200545 and 2200537. + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()).Returns( + new List { + { new LockInfoTdo.Builder { Id = 2200545, State = LockitLockingState.Open }.Build() }, + { new LockInfoTdo.Builder { Id = 2200537, State = LockitLockingState.Closed }.Build() } + } + ); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" } )), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var myBikes = new MyBikesPageViewModel( + tinkApp.ActiveUser, + permissions, /* permissions */ + bluetooth, /* bluetooth */ + Device.Android, + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await myBikes.OnAppearing(); + + // Verify behaviour + Received.InOrder(() => + { + permissions.CheckPermissionStatusAsync(); + var glDummy = geolocation.Active.Received().IsGeolcationEnabled; + var btDummy = bluetooth.Received().State; + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()); + }); + + Assert.IsEmpty(myBikes.StatusInfoText); + + Assert.AreEqual(2, myBikes.Count); + Assert.IsTrue(myBikes.IsIdle); + Assert.IsTrue(myBikes.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1545 = myBikes.FirstOrDefault(x => x.Id == "1545") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1537 = myBikes.FirstOrDefault(x => x.Id == "1537") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + Assert.AreEqual("Rented since 06. November 17:53.", bike1545.StateText); + Assert.AreEqual("Rented since 12. October 08:38.", bike1537.StateText); + Assert.AreEqual("Close lock", bike1545.LockitButtonText); + Assert.AreEqual("Open lock & continue renting", bike1537.LockitButtonText); + + Assert.IsFalse(myBikes.IsNoBikesOccupiedVisible); + Assert.IsEmpty(myBikes.NoBikesOccupiedText); + } + + [Test] + public async Task TestConstruct_Droid_NoPermissions_OpenSettings() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + // Fake location permissions not to be set + permissions.CheckPermissionStatusAsync().Returns(Task.FromResult(PermissionStatus.Denied)); + + // Fake anwser on question whether to open permissions dialog + viewService.DisplayAlert(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); + + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()).Returns( + new List { + { new LockInfoTdo.Builder { Id = 2200545, State = LockitLockingState.Open }.Build() }, + { new LockInfoTdo.Builder { Id = 2200537, State = LockitLockingState.Closed }.Build() } + } + ); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var myBikes = new MyBikesPageViewModel( + tinkApp.ActiveUser, + permissions, /* permissions */ + bluetooth, /* bluetooth */ + Device.Android, + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await myBikes.OnAppearing(); + + // Verify behaviour + Received.InOrder(() => + { + permissions.CheckPermissionStatusAsync(); + var glDummy = geolocation.Active.Received().IsGeolcationEnabled; + permissions.RequestPermissionAsync(); // Ask user from permissions. + viewService.DisplayAlert( + "Hint", + "Please allow location sharing so that bike lock/locks can be managed.\r\nOpen sharing dialog?", + "Yes", + "No"); + permissions.OpenAppSettings(); + }); + + Assert.IsEmpty(myBikes.StatusInfoText); + + Assert.AreEqual(2, myBikes.Count); + Assert.IsTrue(myBikes.IsIdle); + Assert.IsTrue(myBikes.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1545 = myBikes.FirstOrDefault(x => x.Id == "1545") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1537 = myBikes.FirstOrDefault(x => x.Id == "1537") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + Assert.AreEqual("Rented since 06. November 17:53.", bike1545.StateText); + Assert.AreEqual("Rented since 12. October 08:38.", bike1537.StateText); + Assert.AreEqual("Search lock", bike1545.LockitButtonText); + Assert.AreEqual("Search lock", bike1537.LockitButtonText); + + Assert.IsFalse(myBikes.IsNoBikesOccupiedVisible); + Assert.IsEmpty(myBikes.NoBikesOccupiedText); + } + + [Test] + public async Task TestConstruct_Droid_NoPermissions() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + // Fake location permissions not to be set + permissions.CheckPermissionStatusAsync().Returns(Task.FromResult(PermissionStatus.Denied)); + + permissions.OpenAppSettings().Throws(); // Ensures that method is not called and fixture succeeds. + + // Fake anwser on question whether to open permissions dialog + viewService.DisplayAlert(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(false); + + // Fake bluetooth answer for locks with id 2200545 and 2200537. + var lockInfoTdo = new List { + { new LockInfoTdo.Builder { Id = 2200545, State = LockitLockingState.Open }.Build() }, + { new LockInfoTdo.Builder { Id = 2200537, State = LockitLockingState.Closed }.Build() } + }; + + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()).Returns(lockInfoTdo); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var myBikes = new MyBikesPageViewModel( + tinkApp.ActiveUser, + permissions, /* permissions */ + bluetooth, /* bluetooth */ + Device.Android, + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await myBikes.OnAppearing(); + + // Verify behaviour + Received.InOrder(() => + { + permissions.CheckPermissionStatusAsync(); + var glDummy = geolocation.Active.Received().IsGeolcationEnabled; + permissions.RequestPermissionAsync(); // Ask user from permissions. + viewService.DisplayAlert( + "Hint", + "Please allow location sharing so that bike lock/locks can be managed.\r\nOpen sharing dialog?", + "Yes", + "No"); + }); + + Assert.IsEmpty(myBikes.StatusInfoText); + + Assert.AreEqual(2, myBikes.Count); + Assert.IsTrue(myBikes.IsIdle); + Assert.IsTrue(myBikes.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1545 = myBikes.FirstOrDefault(x => x.Id == "1545") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1537 = myBikes.FirstOrDefault(x => x.Id == "1537") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + Assert.AreEqual("Rented since 06. November 17:53.", bike1545.StateText); + Assert.AreEqual("Rented since 12. October 08:38.", bike1537.StateText); + Assert.AreEqual("Search lock", bike1545.LockitButtonText); + Assert.AreEqual("Search lock", bike1537.LockitButtonText); + + Assert.IsFalse(myBikes.IsNoBikesOccupiedVisible); + Assert.IsEmpty(myBikes.NoBikesOccupiedText); + } + + [Test] + public async Task TestConstruct_Droid_GeolocationOff() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + // Fake location permissions to be set + permissions.CheckPermissionStatusAsync().Returns(Task.FromResult(PermissionStatus.Granted)); + geolocation.Active.IsGeolcationEnabled.Returns(false); // Fake gps to be off + + // Fake bluetooth answer for locks with id 2200545 and 2200537. + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()).Returns( + new List { + { new LockInfoTdo.Builder { Id = 2200545, State = LockitLockingState.Open }.Build() }, + { new LockInfoTdo.Builder { Id = 2200537, State = LockitLockingState.Closed }.Build() } + } + ); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var myBikes = new MyBikesPageViewModel( + tinkApp.ActiveUser, + permissions, /* permissions */ + bluetooth, /* bluetooth */ + Device.Android, + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await myBikes.OnAppearing(); + + // Verify behaviour + Received.InOrder(() => + { + permissions.CheckPermissionStatusAsync(); + var glDummy = geolocation.Active.Received().IsGeolcationEnabled; + viewService.DisplayAlert( + "Hint", + "Please activate location so that bike lock can be found!", + "OK"); + }); + + Assert.IsEmpty(myBikes.StatusInfoText); + + Assert.AreEqual(2, myBikes.Count); + Assert.IsTrue(myBikes.IsIdle); + Assert.IsTrue(myBikes.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1545 = myBikes.FirstOrDefault(x => x.Id == "1545") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1537 = myBikes.FirstOrDefault(x => x.Id == "1537") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + Assert.AreEqual("Rented since 06. November 17:53.", bike1545.StateText); + Assert.AreEqual("Rented since 12. October 08:38.", bike1537.StateText); + Assert.AreEqual("Search lock", bike1545.LockitButtonText); + Assert.AreEqual("Search lock", bike1537.LockitButtonText); + + Assert.IsFalse(myBikes.IsNoBikesOccupiedVisible); + Assert.IsEmpty(myBikes.NoBikesOccupiedText); + } + + [Test] + public async Task TestConstruct_Droid_BluetoothOff() + { + var geolocation = Substitute.For>(); + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + // Fake location permissions to be set + permissions.CheckPermissionStatusAsync().Returns(Task.FromResult(PermissionStatus.Granted)); + geolocation.Active.IsGeolcationEnabled.Returns(true); // Fake gps to be on + bluetooth.State.Returns(BluetoothState.Off); // Fake bluetooth to be off + + var lockInfoTdo = new List { + { new LockInfoTdo.Builder { Id = 2200545, State = LockitLockingState.Open }.Build() }, + { new LockInfoTdo.Builder { Id = 2200537, State = LockitLockingState.Closed }.Build() } + }; + + locksService.GetLocksStateAsync(Arg.Any>(), Arg.Any()).Returns(lockInfoTdo); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "5781_d47fc786e740ef77d85a24bcb6f0ff97_oiF2kahH", new List { "300001", "300029" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.ShareeFr01_Set1, 1, sessionCookie)), + geolocation, + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var myBikes = new MyBikesPageViewModel( + tinkApp.ActiveUser, + permissions, /* permissions */ + bluetooth, /* bluetooth */ + Device.Android, + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + geolocation.Active, // geolocation + locksService, + tinkApp.Polling, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await myBikes.OnAppearing(); + + // Verify behaviour + Received.InOrder(() => + { + permissions.CheckPermissionStatusAsync(); + var glDummy = geolocation.Active.Received().IsGeolcationEnabled; + var btDummy = bluetooth.Received().State; + viewService.DisplayAlert( + "Hint", + "Please enable Bluetooth to manage bike lock/locks.", + "OK"); + }); + + Assert.IsEmpty(myBikes.StatusInfoText); + + Assert.AreEqual(2, myBikes.Count); + Assert.IsTrue(myBikes.IsIdle); + Assert.IsTrue(myBikes.IsBikesListVisible, "If there are any bikes, list must be visible."); + + var bike1545 = myBikes.FirstOrDefault(x => x.Id == "1545") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + var bike1537 = myBikes.FirstOrDefault(x => x.Id == "1537") as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel; + Assert.AreEqual("Rented since 06. November 17:53.", bike1545.StateText); + Assert.AreEqual("Rented since 12. October 08:38.", bike1537.StateText); + Assert.AreEqual("Search lock", bike1545.LockitButtonText); + Assert.AreEqual("Search lock", bike1537.LockitButtonText); + + Assert.IsFalse(myBikes.IsNoBikesOccupiedVisible); + Assert.IsEmpty(myBikes.NoBikesOccupiedText); + } + + [Test] + public async Task TestConstruct_NoBikesOccupied() + { + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "Invalid_SessionCookie", new List { "TINK" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var myBikes = new MyBikesPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.iOS, + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + new GeolocationMock(), + locksService, + tinkApp.Polling, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await myBikes.OnAppearing(); + + Assert.IsTrue(new List { "Updating...", string.Empty }.Contains(myBikes.StatusInfoText)); + + Assert.AreEqual(0, myBikes.Count); + Assert.IsTrue(myBikes.IsIdle); + Assert.IsFalse(myBikes.IsBikesListVisible, "If there are any bikes, list must be visible."); + + Assert.IsTrue(myBikes.IsNoBikesOccupiedVisible); + Assert.AreEqual("Momentan sind keine Fahrräder auf Benutzer a@b reserviert/ gebucht.", myBikes.NoBikesOccupiedText); + } + + [Test] + public async Task TestConstruct_Offline() + { + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "4da3044c8657a04ba60e2eaa753bc51a", new List { "TINK" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)), + Substitute.For>(), + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => false, // Offline + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var myBikes = new MyBikesPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.iOS, + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + new GeolocationMock(), + locksService, + tinkApp.Polling, + (d, obj) => d(obj), + Substitute.For(), + viewService); + + await myBikes.OnAppearing(); + + Assert.AreEqual("Offline.", myBikes.StatusInfoText); + + Assert.AreEqual(2, myBikes.Count); + Assert.IsTrue(myBikes.IsIdle); + Assert.IsTrue(myBikes.IsBikesListVisible, "If there are any bikes, list must be visible."); + + Assert.AreEqual("Code 2931, location Station 4, rented since 28. November 13:06.", myBikes.FirstOrDefault(x => x.Id == "7").StateText); + Assert.AreEqual("Code 3630, location Station 5, rented since 28. November 11:01.", myBikes.FirstOrDefault(x => x.Id == "8").StateText); + + Assert.IsFalse(myBikes.IsNoBikesOccupiedVisible); + Assert.AreEqual(string.Empty, myBikes.NoBikesOccupiedText); + } + + [Test] + public async Task TestTinkApp_WebConnectCommunicationError() + { + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "4da3044c8657a04ba60e2eaa753bc51a", new List { "TINK" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new TINK.Model.Connector.Connector( + uri, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default automated test envirnoment", + sessionCookie, + mail, + server: new CopriProviderHttps( + uri, + TinkApp.MerchantId, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default automated test envirnoment", + sessionCookie: sessionCookie, + cacheServer: new CopriCallsCacheMemory(sessionCookie: sessionCookie), + httpsServer: new ExceptionServer((msg) => new WebConnectFailureException(msg, new Exception("Source expection."))))), + Substitute.For>(), + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => false, // Offline + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var myBikes = new MyBikesPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.iOS, + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + new GeolocationMock(), + locksService, + tinkApp.Polling, + (d, obj) => d(obj), + Substitute.For(), + viewService) + { + IsReportLevelVerbose = true + }; + + await myBikes.OnAppearing(); + + Assert.AreEqual("Connection interrupted, server unreachable.", myBikes.StatusInfoText); + + Assert.AreEqual(2, myBikes.Count); + Assert.IsTrue(myBikes.IsIdle); + Assert.IsTrue(myBikes.IsBikesListVisible, "If there are any bikes, list must be visible."); + + Assert.AreEqual("Code 2931, location Station 4, rented since 28. November 13:06.", myBikes.FirstOrDefault(x => x.Id == "7").StateText); + Assert.AreEqual("Code 3630, location Station 5, rented since 28. November 11:01.", myBikes.FirstOrDefault(x => x.Id == "8").StateText); + + Assert.IsFalse(myBikes.IsNoBikesOccupiedVisible); + Assert.AreEqual(string.Empty, myBikes.NoBikesOccupiedText); + } + + [Test] + public async Task TestTinkApp_GeneralPurposeError() + { + var locksService = Substitute.For(); + var timeOut = Substitute.For(); + var viewService = Substitute.For(); + var permissions = Substitute.For(); + var bluetooth = Substitute.For(); + + locksService.TimeOut.Returns(timeOut); + timeOut.MultiConnect.Returns(new TimeSpan(0)); + + var tinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.Off } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: locksService.GetType().FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new TINK.Model.User.Account.Account("a@b", "123456789", "4da3044c8657a04ba60e2eaa753bc51a", new List { "TINK" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new TINK.Model.Connector.Connector( + uri, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie, + mail, + server: new CopriProviderHttps( + uri, + TinkApp.MerchantId, + "TestTINKApp/3.0.127 AutomatedTestEnvirnoment/Default", + sessionCookie: sessionCookie, + cacheServer: new CopriCallsCacheMemory(sessionCookie: sessionCookie), + httpsServer: new ExceptionServer((msg) => new Exception(msg)))), + Substitute.For>(), + locksService, + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + permissions, + isConnectedFunc: () => false, // Offline + postAction: (d, obj) => d(obj), + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173), // Current app version. Must be larger or equal 3.0.173 to + whatsNewShownInVersion: null); // Whats new page was never shown. + + var myBikes = new MyBikesPageViewModel( + tinkApp.ActiveUser, + permissions, + bluetooth, + Device.iOS, + () => tinkApp.GetIsConnected(), + (isConnected) => tinkApp.GetConnector(isConnected), + new GeolocationMock(), + locksService, + tinkApp.Polling, + (d, obj) => d(obj), + Substitute.For(), + viewService) + { + IsReportLevelVerbose = true + }; + + await myBikes.OnAppearing(); + + Assert.AreEqual("Connection interrupted.", myBikes.StatusInfoText); + + Assert.AreEqual(2, myBikes.Count); + Assert.IsTrue(myBikes.IsIdle); + Assert.IsTrue(myBikes.IsBikesListVisible, "If there are any bikes, list must be visible."); + + Assert.AreEqual("Code 2931, location Station 4, rented since 28. November 13:06.", myBikes.FirstOrDefault(x => x.Id == "7").StateText); + Assert.AreEqual("Code 3630, location Station 5, rented since 28. November 11:01.", myBikes.FirstOrDefault(x => x.Id == "8").StateText); + + Assert.IsFalse(myBikes.IsNoBikesOccupiedVisible); + Assert.AreEqual(string.Empty, myBikes.NoBikesOccupiedText); + } + } +} diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestViewModelHelper.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestViewModelHelper.cs new file mode 100644 index 0000000..ed93906 --- /dev/null +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestViewModelHelper.cs @@ -0,0 +1,83 @@ +using NUnit.Framework; +using Rhino.Mocks; +using TINK.Model.Bike; +using TINK.Model.Bikes.Bike.BC; +using TINK.ViewModel; +namespace UITest.Fixtures.ObjectTests.ViewModel +{ + + [TestFixture] + public class TestViewModelHelper + { + [Test] + public void TestGetDisplayName_DefinedTypes() + { + var l_oBike = MockRepository.GenerateStub(); + l_oBike.Stub(x => x.WheelType).Return(WheelType.Two); + l_oBike.Stub(x => x.TypeOfBike).Return(TypeOfBike.Citybike); + l_oBike.Stub(x => x.Description).Return("MeinStadtrad"); + l_oBike.Stub(x => x.Id).Return("22"); + Assert.AreEqual( + "MeinStadtrad", + l_oBike.GetDisplayName()); + + Assert.AreEqual( + "22", + l_oBike.GetDisplayId()); + + l_oBike = MockRepository.GenerateStub(); + l_oBike.Stub(x => x.WheelType).Return(WheelType.Two); + l_oBike.Stub(x => x.TypeOfBike).Return(TypeOfBike.Citybike); + l_oBike.Stub(x => x.Id).Return("22"); + l_oBike.Stub(x => x.IsDemo).Return(true); + l_oBike.Stub(x => x.Description).Return("MeinStadtrad"); + Assert.AreEqual( + "MeinStadtrad", + l_oBike.GetDisplayName()); + Assert.AreEqual( + "22", + l_oBike.GetDisplayId()); + + l_oBike = MockRepository.GenerateStub(); + l_oBike.Stub(x => x.WheelType).Return(WheelType.Trike); + l_oBike.Stub(x => x.TypeOfBike).Return(TypeOfBike.Cargo); + l_oBike.Stub(x => x.Description).Return("MeinCargoDreiradl"); + l_oBike.Stub(x => x.Id).Return("22"); + Assert.AreEqual( + "MeinCargoDreiradl", + l_oBike.GetDisplayName()); + Assert.AreEqual( + "22", + l_oBike.GetDisplayId()); + + l_oBike = MockRepository.GenerateStub(); + l_oBike.Stub(x => x.WheelType).Return(WheelType.Two); + l_oBike.Stub(x => x.TypeOfBike).Return(TypeOfBike.Cargo); + l_oBike.Stub(x => x.Description).Return("MeinCargoALololong"); + l_oBike.Stub(x => x.Id).Return("22"); + Assert.AreEqual( + "MeinCargoALololong", + l_oBike.GetDisplayName()); + Assert.AreEqual( + "22", + l_oBike.GetDisplayId()); + } + + [Test] + public void TestGetDisplayName_UndefinedTypes() + { + var l_oBike = MockRepository.GenerateStub(); + l_oBike.Stub(x => x.WheelType).Return(WheelType.Mono); + l_oBike.Stub(x => x.TypeOfBike).Return(TypeOfBike.Cargo); + l_oBike.Stub(x => x.Description).Return("SuperCargo"); + l_oBike.Stub(x => x.Id).Return("22"); + Assert.AreEqual( + "SuperCargo", + l_oBike.GetDisplayName()); + + Assert.AreEqual( + "22", + l_oBike.GetDisplayId()); + } + } +} diff --git a/TestTINKLib/Fixtures/UseCases/ConnectedOffline/TestTinkApp.cs b/TestTINKLib/Fixtures/UseCases/ConnectedOffline/TestTinkApp.cs new file mode 100644 index 0000000..67296d5 --- /dev/null +++ b/TestTINKLib/Fixtures/UseCases/ConnectedOffline/TestTinkApp.cs @@ -0,0 +1,40 @@ +using NUnit.Framework; +using System; +using TINK.Model; +using TINK.Repository; + +namespace TestTINKLib.Fixtures.UseCases.ConnectedOffline +{ + using NSubstitute; + using TestTINKLib.Mocks.Device; + using TestTINKLib.Mocks.Services; + using TestTINKLib.Model.User.Account; + using TINK.Model.Connector; + using TINK.Model.Services.Geolocation; + using TINK.Services; + using static TINK.Repository.CopriCallsMemory; + + [TestFixture] + public class TestTinkApp + { + [Test, Explicit("Draft")] + public void TestConstruct() + { + var l_oApp = new TinkApp( + new TINK.Model.Settings.Settings( + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173)); // Current app version. Must be larger or equal 3.0.173 to + } + } +} diff --git a/TestTINKLib/Fixtures/UseCases/SelectStation/TestTinkApp.cs b/TestTINKLib/Fixtures/UseCases/SelectStation/TestTinkApp.cs new file mode 100644 index 0000000..1bfceb2 --- /dev/null +++ b/TestTINKLib/Fixtures/UseCases/SelectStation/TestTinkApp.cs @@ -0,0 +1,72 @@ +using NUnit.Framework; +using TINK.Model; +using TestTINKLib.Model.User.Account; +using TINK.Model.Connector; +using TestTINKLib.Mocks.Device; +using System; +using static TINK.Repository.CopriCallsMemory; +using System.Collections.Generic; +using TestTINKLib.Mocks.Services; +using TINK.Repository; +using TINK.ViewModel.Settings; +using NSubstitute; +using TINK.Services; +using TINK.Model.Services.Geolocation; + +namespace TestTINKLib.Fixtures.UseCases.SelectStation +{ + + [TestFixture] + public class TestTinkApp + { + [Test] + public void TestBikesAtStation_AccountStoreMock_NoUser_CopriMock_Set2() + { + var l_oConnector = new ConnectorCache( + string.Empty, + string.Empty, + new CopriCallsMemory(SampleSets.Set2, 1)); + + var l_oTinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new TINK.ViewModel.Map.GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + currentVersion: new Version(3, 2, 0, 115), + lastVersion: new Version(3, 0, 173)); // Current app version. Must be larger or equal 3.0.173 to + + Assert.AreEqual(0, TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.Count); + + l_oTinkApp.SelectedStation = new TINK.Model.Station.Station("5", new List(), null); + Assert.AreEqual(3, TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.Count); + Assert.AreEqual("25", TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.GetById("25").Id); + Assert.AreEqual("11", TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.GetById("11").Id); + Assert.AreEqual("2", TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.GetById("2").Id); + + l_oTinkApp.SelectedStation = new TINK.Model.Station.Station("10", new List(), null); + + Assert.AreEqual( + 1, + TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.Count); + + Assert.AreEqual("18", TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.GetById("18").Id); + + l_oTinkApp.SelectedStation = new TINK.Model.Station.Station("91345", new List(), null); + + Assert.AreEqual(0, TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.Count); + } + } +} \ No newline at end of file diff --git a/TestTINKLib/Fixtures/UseCases/Startup/TestTinkApp.cs b/TestTINKLib/Fixtures/UseCases/Startup/TestTinkApp.cs new file mode 100644 index 0000000..bfbe336 --- /dev/null +++ b/TestTINKLib/Fixtures/UseCases/Startup/TestTinkApp.cs @@ -0,0 +1,386 @@ +using NUnit.Framework; +using TINK.Model; + +using TINK.Model.User.Account; +using TestTINKLib.Model.User.Account; +using TINK.Model.Connector; +using TestTINKLib.Mocks.Device; +using System; +using TINK.Model.Station; +using TINK.Model.Bike; +using static TINK.Repository.CopriCallsMemory; +using TINK.Model.State; +using System.Collections.Generic; +using TestTINKLib.Mocks.Services; +using TINK.Repository; +using TINK.ViewModel.Map; +using TINK.ViewModel.Settings; +using NSubstitute; +using TINK.Services; +using TINK.Model.Services.Geolocation; + +namespace TestTINKLib.Fixtures.UseCases.Startup +{ + [TestFixture] + public class TestTinkApp + { + [Test] + public void NotLoggedIn() + { + var l_oConnector = new ConnectorCache( + string.Empty, + string.Empty, + new CopriCallsMemory(SampleSets.Set2, 1)); + + var l_oTinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://tinkwwp.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + currentVersion: new Version(3, 2, 0, 115), // Current app version + lastVersion: new Version(3, 0, 173)); // Current app version. Must be larger or equal 3.0.173 to + + var l_oStations = l_oTinkApp.GetConnector(true).Query.GetBikesAndStationsAsync().Result.Response; + + // Check stations. + Assert.AreEqual(8, l_oStations.StationsAll.Count); + Assert.NotNull(l_oStations.StationsAll.GetById("4")); + Assert.AreEqual("4", l_oStations.StationsAll.GetById("4").Id); + Assert.AreEqual(new Position(47.6586936667, 9.16863116667), l_oStations.StationsAll.GetById("4").Position); + + // Verify selected station. + Assert.AreEqual( + null, + l_oTinkApp.SelectedStation.Id, + "When starting app selected station must always be invlid"); + + // Verify bikes at station. + Assert.AreEqual( + 0, + TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.Count, + "If no station is selected BikesAtStation- list count must be zero"); + + // Check my bikes. + Assert.IsFalse(l_oTinkApp.ActiveUser.IsLoggedIn); + } + + [Test] + public void LoggedIn_BikesReservedBooked() + { + var l_oConnector = new ConnectorCache( + "4da3044c8657a04ba60e2eaa753bc51a", + "javaminister@gmail.com", + new CopriCallsMemory(SampleSets.Set2, 1, "4da3044c8657a04ba60e2eaa753bc51a")); + + // User logged in is initial state to verify. + var l_oTinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://tinkwwp.copri-bike.de/APIjsonserver"), new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new Account("javaminister@gmail.com", "javaminister" /* password */, "4da3044c8657a04ba60e2eaa753bc51a" /* session cookie */, new List { "TINK", "Konrad" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1, "4da3044c8657a04ba60e2eaa753bc51a")), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + currentVersion: new Version(3, 2, 0, 115), + lastVersion: new Version(3, 0, 173)); // Current app version. Must be larger or equal 3.0.173 to + + var l_oStations = l_oTinkApp.GetConnector(true).Query.GetBikesAndStationsAsync().Result.Response; + + // Check stations. + Assert.AreEqual(8, l_oStations.StationsAll.Count); + Assert.NotNull(l_oStations.StationsAll.GetById("4")); + Assert.AreEqual("4", l_oStations.StationsAll.GetById("4").Id); + Assert.AreEqual(new Position(47.6586936667, 9.16863116667), l_oStations.StationsAll.GetById("4").Position); + + // Verify selected station. + Assert.AreEqual( + null, + l_oTinkApp.SelectedStation.Id, + "When starting app selected station must always be invlid"); + + // Verify bikes at station. + Assert.AreEqual( + 0, + TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.Count, + "If no station is selected BikesAtStation- list count must be zero"); + + // Check my bikes + var l_oBikes = l_oTinkApp.GetConnector(true).Query.GetBikesOccupiedAsync().Result.Response; + Assert.AreEqual(2, l_oBikes.Count); + Assert.AreEqual("8", l_oBikes.GetById("8").Id); + Assert.AreEqual(InUseStateEnum.Booked, l_oBikes.GetById("8").State.Value); + Assert.AreEqual("5", l_oBikes.GetById("8").CurrentStation); + Assert.AreEqual(TypeOfBike.Cargo, l_oBikes.GetById("8").TypeOfBike); + Assert.AreEqual(WheelType.Two, l_oBikes.GetById("8").WheelType); + Assert.AreEqual("7", l_oBikes.GetById("7").Id); + } + + [Test] + public void LoggedIn_AllBikesDisposable_FilterTINK() + { + var l_oConnector = new ConnectorCache( + "1234", + "mgrimm@gmail.com", + new CopriCallsMemory(SampleSets.Set2, 1)); + + // No user logged in is initial state to verify. + var l_oTinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://app.tink-konstanz.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new Account("mgrimm@gmail.com", "123456789" /* password */, "1234" /* session cookie */, new List { "TINK", "Konrad" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + currentVersion: new Version(3, 2, 0, 115), + lastVersion: new Version(3, 0, 173)); // Current app version. Must be larger or equal 3.0.173 to + + var l_oStations = l_oTinkApp.GetConnector(true).Query.GetBikesAndStationsAsync().Result.Response; + + // Check stations. + Assert.AreEqual(8, l_oStations.StationsAll.Count); + Assert.NotNull(l_oStations.StationsAll.GetById("4")); + Assert.AreEqual("4", l_oStations.StationsAll.GetById("4").Id); + Assert.AreEqual(new Position(47.6586936667, 9.16863116667), l_oStations.StationsAll.GetById("4").Position); + + // Verify selected station. + Assert.AreEqual( + null, + l_oTinkApp.SelectedStation.Id, + "When starting app selected station must always be invlid"); + + // Verify bikes at station. + Assert.AreEqual( + 0, + TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.Count, + "If no station is selected BikesAtStation- list count must be zero"); + + // Check my bikes + Assert.IsTrue(l_oTinkApp.ActiveUser.IsLoggedIn); + } + + [Test] + public void LoggedIn_AllBikesDisposable_FilterKonrad() + { + var l_oCopriServer = new CopriCallsMemory(SampleSets.Set2, 1); + var l_oConnector = new ConnectorCache( + "1234", + "mgrimm@gmail.com", + new CopriCallsMemory(SampleSets.Set2, 1)); + + // No user logged in is initial state to verify. + var l_oTinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "Konrad", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://app.tink-konstanz.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(LocksServiceMock).FullName, + activeGeolocationService: typeof(GeolocationMock).FullName), + new StoreMock(new Account("mgrimm@gmail.com", "123456789" /* password */, "1234" /* session cookie */, new List { "TINK", "Konrad" })), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1)), + Substitute.For>(), + new LocksServiceMock(), + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), + isConnectedFunc: () => true, + currentVersion: new Version(3, 2, 0, 115), + lastVersion: new Version(3, 0, 173)); // Current app version. Must be larger or equal 3.0.173 to + + var l_oStations = l_oTinkApp.GetConnector(true).Query.GetBikesAndStationsAsync().Result.Response; + + // Check stations. + Assert.AreEqual(2, l_oStations.StationsAll.Count); + Assert.NotNull(l_oStations.StationsAll.GetById("14")); + Assert.AreEqual("14", l_oStations.StationsAll.GetById("14").Id); + Assert.AreEqual(new Position(47.66698054007847, 9.169303178787231), l_oStations.StationsAll.GetById("14").Position); + + // Verify selected station. + Assert.AreEqual( + null, + l_oTinkApp.SelectedStation.Id, + "When starting app selected station must always be invlid"); + + // Verify bikes at station. + Assert.AreEqual( + 0, + TestHelper.GetBikesAtStation(l_oTinkApp.ActiveUser, l_oConnector, l_oTinkApp.SelectedStation.Id).Result.Count, + "If no station is selected BikesAtStation- list count must be zero"); + + // Check my bikes + Assert.IsTrue(l_oTinkApp.ActiveUser.IsLoggedIn); + } + + [Test, Ignore("Todo: Check if reserved bikes are released even if system is offline.")] + public void LoggedIn_BikesReserved_OfflineAfterStart() + { + } + + /// + /// Test if Whats New logic works as expected. Form version 3.0.0.115 or smaller no Whats New dialog existed. + /// + [Test] + public void FirstInstall_WhatsNew() + { + var l_oAppVersion = new Version(3, 2, 0, 120); + + var l_oTinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1)), + Substitute.For>(), + null, // use default locks service + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), +#if ARENDI + Substitute.For(), +#endif + isConnectedFunc: () => true, + currentVersion: l_oAppVersion, // Current app version + lastVersion: null, // Last version. + whatsNewShownInVersion: null); // First install. + + // Whats new page + Assert.IsFalse( + l_oTinkApp.WhatsNew.IsShowRequired, + $"Whats new should not be displayed because first install has been detected."); + + // Call member which view model calls when using app. + l_oTinkApp.SetWhatsNewWasShown(); + + // Whats new page + Assert.IsFalse( + l_oTinkApp.WhatsNew.IsShowRequired, + $"Whats new should mot be displayed first install detected."); + } + + [Test] + public void Upgrade120_WhatsNew() + { + var l_oAppVersion = new Version(3, 0, 0, 120); + Version lastVersion = new Version(3, 0, 0, 116); + + var l_oTinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1)), + Substitute.For>(), + null, // use default locks service + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), +#if ARENDI // Requires LockItArendi library. + Substitute.For(), +#endif + isConnectedFunc: () => true, + currentVersion: l_oAppVersion, // Current app version + lastVersion: lastVersion, + whatsNewShownInVersion: null); // Whats new page was never shown. + + // Whats new page + Assert.IsTrue( + l_oTinkApp.WhatsNew.IsShowRequired, + $"Whats new should be displayed because app version is {l_oAppVersion} and version when \"Whats New\" was shown last is {lastVersion} (null means never)."); + + // Call member which view model calls when using app. + l_oTinkApp.SetWhatsNewWasShown(); + + // Whats new page + Assert.IsFalse( + l_oTinkApp.WhatsNew.IsShowRequired, + $"Whats new should mot be displayed because app version is {l_oAppVersion} and version when \"Whats New\" was shown last is {lastVersion} equals."); + } + + [Test] + public void SubsequentStart_WhatsNew() + { + var l_oAppVersion = new Version(3, 2, 0, 115); + var l_oWhatsNewVersion = new Version(3, 2, 0, 115); + + var l_oTinkApp = new TinkApp( + new TINK.Model.Settings.Settings( + new GroupFilterMapPage(new Dictionary { { "TINK", FilterState.On } }), + new GroupFilterSettings(new Dictionary { { "TINK", FilterState.On }, { "Konrad", FilterState.On } }), + new Uri("https://shareeapp-primary.copri-bike.de/APIjsonserver"), + new TINK.Settings.PollingParameters(new TimeSpan(10000), true), + Serilog.Events.LogEventLevel.Error, + activeLockService: typeof(TINK.Services.BluetoothLock.BLE.LockItByGuidService).FullName, + activeGeolocationService: typeof(TINK.Model.Services.Geolocation.LastKnownGeolocationService).FullName), + new StoreMock(), + (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(sessionCookie, mail, new CopriCallsMemory(SampleSets.Set2, 1)), + Substitute.For>(), + null, // use default locks service + new DeviceMock(), + new SpecialFolderMock(), + null, // Cipher + new PermissionsMock(), +#if ARENDI // Requires LockItArendi library. + Substitute.For(), +#endif + isConnectedFunc: () => true, + currentVersion: l_oAppVersion, // Current app version< + lastVersion: l_oWhatsNewVersion); // Whats new page was never shown. + + // Whats new page + Assert.IsFalse( + l_oTinkApp.WhatsNew.IsShowRequired, + $"Whats new should not be displayed because app version is {l_oAppVersion} and version when \"Whats New\" was shown last is {l_oWhatsNewVersion} equals."); + + // Call member which view model would call if Whats New would have been shonw. + l_oTinkApp.SetWhatsNewWasShown(); + + // Whats new page + Assert.IsFalse( + l_oTinkApp.WhatsNew.IsShowRequired, + $"Whats new should mot be displayed because app version is {l_oAppVersion} and version when \"Whats New\" was shown last is {l_oWhatsNewVersion} equals."); + } + } +} diff --git a/TestTINKLib/Fixtures/UseCases/TestHelper.cs b/TestTINKLib/Fixtures/UseCases/TestHelper.cs new file mode 100644 index 0000000..cbf85c1 --- /dev/null +++ b/TestTINKLib/Fixtures/UseCases/TestHelper.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using TINK.Model; +using TINK.Model.Bike; +using TINK.Model.User; + +namespace TestTINKLib.Fixtures.UseCases +{ + public class TestHelper + { + /// + /// Get all bikes at a given station from copri. + /// + public static async Task GetBikesAtStation( + User user, + TINK.Model.Connector.IConnector connector, + string selectedStation) + { + var l_oBikesAtStation = new BikeCollectionMutable(); + + var l_oBikesAvailable = (await connector.Query.GetBikesAsync()).Response; + + l_oBikesAtStation.Update(l_oBikesAvailable.GetAtStation(selectedStation)); + + return l_oBikesAtStation; + } + } +} diff --git a/TestTINKLib/LoginSessionCopriInfo.cs b/TestTINKLib/LoginSessionCopriInfo.cs new file mode 100644 index 0000000..5725f2d --- /dev/null +++ b/TestTINKLib/LoginSessionCopriInfo.cs @@ -0,0 +1,65 @@ +namespace TestTINKLib +{ + /// + /// Object do define login session info from copri. + /// + public struct LoginSessionCopriInfo + { + public LoginSessionCopriInfo( + string p_strMail, + string p_strPwd, + string p_strAuthCookie, + string p_strDeviceId) + { + Mail = p_strMail; + Pwd = p_strPwd; + AuthCookie = p_strAuthCookie; + DeviceId = p_strDeviceId; + } + + public string Mail { get; private set; } + public string Pwd { get; private set; } + public string AuthCookie { get; private set; } + public string DeviceId { get; private set; } + + /// + /// First test user which typically has some bikes. + /// + public static LoginSessionCopriInfo JavaministerHardwareNr1 = new LoginSessionCopriInfo( + "javaminister@gmail.com", + "javaminister", + "6103_4da3044c8657a04ba60e2eaa753bc51a_", + "HwId1000000000000" + ); + + /// + /// First test user which typically has some bikes. + /// + public static LoginSessionCopriInfo SkiministerHardwareNr1 = new LoginSessionCopriInfo( + "skiminister@posteo.de", + "skiminister", + "", + "HwId1000000000000" + ); + + /// + /// User which does not exist in copri. + /// + public static LoginSessionCopriInfo JavaministerGibtsNet = new LoginSessionCopriInfo( + "javaminister@gmail.com.GibtsNet", + "javaminister", + "", + "HwId1000000000000" + ); + + /// + /// User which does not exist in copri. + /// + public static LoginSessionCopriInfo JavaministerKeksGibtsNet = new LoginSessionCopriInfo( + "javaminister@gmail.com", + "javaminister", + "6103_ThisKeksDoesNotExist_", + "HwId1000000000000" + ); + } +} diff --git a/TestTINKLib/Mocks/Bike/BikeCollectionMock.cs b/TestTINKLib/Mocks/Bike/BikeCollectionMock.cs new file mode 100644 index 0000000..2c880cb --- /dev/null +++ b/TestTINKLib/Mocks/Bike/BikeCollectionMock.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.ObjectModel; +using TINK.Model.Bike; + +using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable; + +namespace TestTINKLib.Mocks.Bike +{ + public class BikeCollectionMock : Collection, IBikeDictionaryMutable + { + public BikeInfoMutable GetById(string id) + { + return null; + } + + public void RemoveById(string id) + { + throw new NotSupportedException(); + } + + public bool ContainsKey(string id) + { + throw new NotSupportedException(); + } + } +} diff --git a/TestTINKLib/Mocks/Connector/CopriCallsCacheMemory.cs b/TestTINKLib/Mocks/Connector/CopriCallsCacheMemory.cs new file mode 100644 index 0000000..a5ad07a --- /dev/null +++ b/TestTINKLib/Mocks/Connector/CopriCallsCacheMemory.cs @@ -0,0 +1,106 @@ +using System; +using System.Threading.Tasks; +using TINK.Model.Services.CopriApi; +using TINK.Repository; +using TINK.Repository.Response; +using static TINK.Repository.CopriCallsMemory; +using TINK.Repository.Request; +using TINK.Model.Device; + +namespace TestTINKLib.Mocks.Connector +{ + /// Allows use of memory for retrieving defined respones. + public class CopriCallsCacheMemory : ICopriCache + { + private CopriCallsMemory server; + + public CopriCallsCacheMemory( + SampleSets? sampleSet = null, + int? index = null, + string sessionCookie = null) + { + server = new CopriCallsMemory(sampleSet, index, sessionCookie); + } + + public bool IsStationsExpired => true; + + public bool IsBikesAvailableExpired => true; + + public bool IsBikesOccupiedExpired => true; + + public bool IsConnected => server.IsConnected; + + public string SessionCookie => server.SessionCookie; + + public string MerchantId => server.MerchantId; + + public void AddToCache(StationsAllResponse stations) + { + return; + } + + public void AddToCache(BikesAvailableResponse bikes) + { + return; + } + + public void AddToCache(BikesReservedOccupiedResponse bikes) + { + return; + } + + public Task DoAuthorizationAsync(string p_strMailAddress, string p_strPassword, string p_strDeviceId) + { + throw new NotImplementedException(); + } + + public Task DoAuthoutAsync() + { + throw new NotImplementedException(); + } + + public Task DoReserveAsync(string bikeId, Uri operatorUri) + { + throw new NotImplementedException(); + } + + public Task DoCancelReservationAsync(string p_iBikeId, Uri operatorUri) + { + throw new NotImplementedException(); + } + + public Task CalculateAuthKeysAsync(string bikeId, Uri operatorUri) + => throw new NotSupportedException(); + + public Task UpdateLockingStateAsync(string bikeId, LocationDto geolocation, lock_state state, double batteryPercentage, Uri operatorUri) + => throw new NotImplementedException(); + + public Task DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri) + { + throw new NotImplementedException(); + } + + public Task DoReturn(string bikeId, LocationDto location, ISmartDevice smartDevice, Uri operatorUri) + { + throw new NotImplementedException(); + } + + public Task DoSubmitFeedback(string bikeId, string message, bool isBikeBroken, Uri operatorUri) + => throw new NotImplementedException(); + + public Task GetBikesAvailableAsync() + { + return server.GetBikesAvailableAsync(); + } + + public Task GetBikesOccupiedAsync() + { + return server.GetBikesOccupiedAsync(); + } + + public Task GetStationsAsync() + { + return server.GetStationsAsync(); + } + } +} diff --git a/TestTINKLib/Mocks/Connector/ExceptionServer.cs b/TestTINKLib/Mocks/Connector/ExceptionServer.cs new file mode 100644 index 0000000..553f129 --- /dev/null +++ b/TestTINKLib/Mocks/Connector/ExceptionServer.cs @@ -0,0 +1,82 @@ +using System; +using System.Threading.Tasks; +using TINK.Model.Device; +using TINK.Repository; +using TINK.Repository.Request; +using TINK.Repository.Response; + +namespace TestTINKLib.Mocks.Connector +{ + /// Simulates communication errors when connecting to copri. + public class ExceptionServer : ICopriServer + { + private Func ExceptionFactory { get; } + + /// Constructs object. + /// Provides exceptions which are thrown whenn querrying information from server. + public ExceptionServer(Func exceptionFactory) + { + ExceptionFactory = exceptionFactory; + } + + public bool IsConnected => true; + + public string SessionCookie => string.Empty; + + public string MerchantId => string.Empty; + + public Task DoAuthorizationAsync(string p_strMailAddress, string p_strPassword, string p_strDeviceId) + { + throw new NotImplementedException(); + } + + public Task DoAuthoutAsync() + { + throw new NotImplementedException(); + } + + public Task DoReserveAsync(string bikeId, Uri operatorUri) + { + throw new NotImplementedException(); + } + + public Task DoCancelReservationAsync(string p_iBikeId, Uri operatorUri) + { + throw new NotImplementedException(); + } + + public Task CalculateAuthKeysAsync(string bikeId, Uri operatorUri) + => throw new NotImplementedException(); + + public Task UpdateLockingStateAsync(string bikeId, LocationDto geolocation, lock_state state, double batteryPercentage, Uri operatorUri) + => throw new NotImplementedException(); + + public Task DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri) + { + throw new NotImplementedException(); + } + + public Task DoReturn(string bikeId, LocationDto location, ISmartDevice smartDevice, Uri operatorUri) + { + throw new NotImplementedException(); + } + + public Task DoSubmitFeedback(string bikeId, string message, bool isBikeBroken, Uri operatorUri) + => throw new NotImplementedException(); + + public Task GetBikesAvailableAsync() + { + throw ExceptionFactory($"Simulated error thrown at {nameof(GetBikesAvailableAsync)}."); + } + + public Task GetBikesOccupiedAsync() + { + throw ExceptionFactory($"Simulated error thrown at {nameof(GetBikesOccupiedAsync)}."); + } + + public Task GetStationsAsync() + { + throw ExceptionFactory($"Simulated error thrown at {nameof(GetStationsAsync)}."); + } + } +} diff --git a/TestTINKLib/Mocks/DateTimeMocker.cs b/TestTINKLib/Mocks/DateTimeMocker.cs new file mode 100644 index 0000000..74be814 --- /dev/null +++ b/TestTINKLib/Mocks/DateTimeMocker.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace TestTINKLib +{ + public class DateTimeMocker + { + private static int m_iIndex; + private static IList m_oDateTimeSeries; + + public DateTimeMocker(IList p_oDateTimeSeries) + { + if (p_oDateTimeSeries.Count < 1) + { + throw new Exception("Can not initialize mock object. List must contain at least one date time."); + } + + m_iIndex = 0; + m_oDateTimeSeries = p_oDateTimeSeries; + } + + public Func GetDateTime = () => m_oDateTimeSeries[m_iIndex < m_oDateTimeSeries.Count ? m_iIndex++ : m_iIndex = 0]; + } +} diff --git a/TestTINKLib/Mocks/Device/DeviceMock.cs b/TestTINKLib/Mocks/Device/DeviceMock.cs new file mode 100644 index 0000000..f40753d --- /dev/null +++ b/TestTINKLib/Mocks/Device/DeviceMock.cs @@ -0,0 +1,46 @@ +using TINK.Model.Device; + +namespace TestTINKLib.Mocks.Device +{ + public class DeviceMock : ISmartDevice + { + /// + /// Holds the id of the device. + /// + private string m_strDeviceId = "522c6ff6886198fd"; + + public string Manufacturer => throw new System.NotImplementedException(); + + public string Model => throw new System.NotImplementedException(); + + public string PlatformText => throw new System.NotImplementedException(); + + public string VersionText => throw new System.NotImplementedException(); + + /// + /// Constructs a device mock object setting device id to default value. + /// + public DeviceMock() + { + } + + /// + /// Constructs a device mock object. + /// + /// Mocked Id + public DeviceMock(string p_strDeviceId) + { + m_strDeviceId = p_strDeviceId; + } + + /// Gets the device ID. + /// + public string Identifier + => m_strDeviceId; + + /// Close the application. + public void CloseApplication() + { + } + } +} diff --git a/TestTINKLib/Mocks/Device/SpecialFolderMock.cs b/TestTINKLib/Mocks/Device/SpecialFolderMock.cs new file mode 100644 index 0000000..333fb1d --- /dev/null +++ b/TestTINKLib/Mocks/Device/SpecialFolderMock.cs @@ -0,0 +1,23 @@ +using TINK.Model.Device; + +namespace TestTINKLib.Mocks.Device +{ + public class SpecialFolderMock : ISpecialFolder + { + /// + /// Get the folder name of external folder to write to. + /// + /// + public string GetExternalFilesDir() + { + return string.Empty; + } + + /// Gets the folder name of the personal data folder dir on internal storage. + /// Directory name. + public string GetInternalPersonalDir() + { + return System.IO.Path.GetTempPath(); + } + } +} diff --git a/TestTINKLib/Mocks/Services/GeolocationMock.cs b/TestTINKLib/Mocks/Services/GeolocationMock.cs new file mode 100644 index 0000000..adcfd85 --- /dev/null +++ b/TestTINKLib/Mocks/Services/GeolocationMock.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading.Tasks; +using TINK.Model.Services.Geolocation; +using Xamarin.Essentials; + +namespace TestTINKLib.Mocks.Services +{ + public class GeolocationMock : IGeolocation + { + + public Task GetAsync(DateTime? timeStamp = null) + { + throw new NotImplementedException(); + } + + /// If true location data returned is simulated. + public bool IsSimulation { get => true; } + + public bool IsGeolcationEnabled => true; + } +} diff --git a/TestTINKLib/Mocks/Services/LocksServiceMock.cs b/TestTINKLib/Mocks/Services/LocksServiceMock.cs new file mode 100644 index 0000000..a0c91c9 --- /dev/null +++ b/TestTINKLib/Mocks/Services/LocksServiceMock.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using TINK.Model.Bike; +using TINK.Model.Bike.BluetoothLock; +using TINK.Services.BluetoothLock; +using TINK.Services.BluetoothLock.Tdo; + +namespace TestTINKLib.Mocks.Services +{ + public class LocksServiceMock : ILocksService + { + /// Timeout for connect operation of a single lock. + public async Task> GetLocksStateAsync(IEnumerable locksInfo, TimeSpan connectTimeout) + { + return await Task.FromResult(new List()); + } + + /// Holds timeout values for series of connecting attemps to a lock or multiple locks. + public ITimeOutProvider TimeOut { get; set; } + + public void UpdateSimulation(BikeCollection bikes) {} + + /// Opens lock. + /// Bike object to update. + public async Task OpenAsync(int lockId, byte[] copriKey) => await Task.FromResult(LockitLockingState.Open); + + /// Closes lock. + /// Bike object to update. + public async Task CloseAsync(int lockId, byte[] copriKey) => await Task.FromResult(LockitLockingState.Closed); + + /// Gets the state of a bike. + /// Id of lockId to get state for. + /// + public async Task GetState(string lockId) => await Task.FromResult((LockitLockingState?)null); + + /// Connects to lock. + /// Info required to connect to lock. + /// Timeout for connect operation. + public async Task ConnectAsync(LockInfoAuthTdo authInfo, TimeSpan connectTimeout) + { + return await Task.FromResult(new LockInfoTdo.Builder { Id = 12, Guid = new System.Guid("00000000-0000-0000-0000-000000000042"), State = null }.Build()); + } + + /// Set sound settings. + /// Id of lock to set sound settings. + public async Task SetSoundAsync(int lockId, SoundSettings settings) + { + return await Task.FromResult(true); + } + + /// Gets battery percentage. + /// Id of lock to get info for. + public Task GetBatteryPercentageAsync(int lockId) + { + throw new NotSupportedException(); + } + + /// Sets whether alarm is on or off. + /// Id of lock to get info from. + public async Task SetIsAlarmOffAsync(int lockId, bool activated) + { + await Task.FromResult(true); + } + + /// Gets whether alarm is on or off. + /// Id of lock to get info for. + public async Task GetIsAlarmOffAsync(int lockId) + { + return await Task.FromResult(true); + } + + /// Gets a lock by bike Id. + /// + /// Lock object + public ILockService this[int bikeId] + { + get + { + return null; + } + } + + public Task DisconnectAsync(int bikeId, Guid bikeGuid) => throw new NotSupportedException(); + } +} diff --git a/TestTINKLib/Mocks/Services/PermissionsMock.cs b/TestTINKLib/Mocks/Services/PermissionsMock.cs new file mode 100644 index 0000000..cca42fe --- /dev/null +++ b/TestTINKLib/Mocks/Services/PermissionsMock.cs @@ -0,0 +1,41 @@ +using Plugin.Permissions; +using Plugin.Permissions.Abstractions; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace TestTINKLib.Mocks.Services +{ + public class PermissionsMock : IPermissions + { + public async Task CheckPermissionStatusAsync() where T : BasePermission, new() + { + return await Task.FromResult(Plugin.Permissions.Abstractions.PermissionStatus.Granted); + } + + public Task CheckPermissionStatusAsync(Permission permission) + { + throw new NotImplementedException(); + } + + public bool OpenAppSettings() + { + throw new NotImplementedException(); + } + + public Task RequestPermissionAsync() where T : BasePermission, new() + { + throw new NotImplementedException(); + } + + public Task> RequestPermissionsAsync(params Permission[] permissions) + { + throw new NotImplementedException(); + } + + public Task ShouldShowRequestPermissionRationaleAsync(Permission permission) + { + throw new NotImplementedException(); + } + } +} diff --git a/TestTINKLib/Mocks/User/Account/StoreMock.cs b/TestTINKLib/Mocks/User/Account/StoreMock.cs new file mode 100644 index 0000000..3b69e5e --- /dev/null +++ b/TestTINKLib/Mocks/User/Account/StoreMock.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using TINK.Model.User.Account; + +namespace TestTINKLib.Model.User.Account +{ + public class StoreMock : IStore + { + IAccount m_oAccount; + + /// + /// Instantiates + /// - a dummy account to simulate a logged in user before end of last session + /// - an empty account to simulate no user logged in before end of last session + /// + /// If null no user is logged in. + public StoreMock(IAccount p_oAccount = null) + { + m_oAccount = new TINK.Model.User.Account.Account(p_oAccount ?? new EmptyAccount()); + } + + public Task Load() + { + return Task.FromResult(new TINK.Model.User.Account.Account(m_oAccount)); + } + + public IAccount Delete(IAccount p_oAccount) + { + // Set member to empty. + m_oAccount = new EmptyAccount(); + + // Return empty account. + return new EmptyAccount(); + } + + public Task Save(IAccount p_oAccount) + { + m_oAccount = new TINK.Model.User.Account.Account(p_oAccount); + return Task.CompletedTask; + } + } +} diff --git a/TestTINKLib/TestHelper.cs b/TestTINKLib/TestHelper.cs new file mode 100644 index 0000000..a119d2f --- /dev/null +++ b/TestTINKLib/TestHelper.cs @@ -0,0 +1,18 @@ +namespace TestTINKLib +{ + /// + /// Provides helper functionality. Used in TestShareeLib and TestTINKLib. + /// + public static class TestHelper + { + /// + /// Eases comparison of xml- files. + /// + /// + /// + public static string PrepareXmlForStringCompare(string p_strXmlFile) + { + return p_strXmlFile.Replace("\r\n", string.Empty).Replace("\t", string.Empty).Replace(" ", string.Empty); + } + } +} diff --git a/TestTINKLib/TestTINKLib.csproj b/TestTINKLib/TestTINKLib.csproj new file mode 100644 index 0000000..5faaaf0 --- /dev/null +++ b/TestTINKLib/TestTINKLib.csproj @@ -0,0 +1,222 @@ + + + + Debug + AnyCPU + {730A31A5-6736-43CC-8F84-8FDA5093E283} + Library + TestTINKLib + TestTINKLib + v4.6.1 + + + + 3.0 + + + true + full + false + bin\Debug + DEBUG;NOLIVESERVER + prompt + 4 + false + + + full + true + bin\Release + prompt + 4 + false + + + + False + ..\LockItArendi\Arendi\Arendi.BleLibrary.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 13.0.1 + + + 4.2.2 + + + 3.13.2 + + + 4.0.0 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + 1.0.2 + + + 3.6.1 + + + 4.1.0 + + + 4.3.0 + + + 4.7.0 + + + 4.3.0 + + + 1.6.1 + + + + + + + + {bde9ce26-15cf-47da-a4f6-b6956d02d0fc} + LockItBLE + + + {3589ED1D-E734-429D-976F-1BEA4371DF14} + LockItShared + + + {b77f4222-0860-4494-a07c-ee8e09fa9983} + TINKLib + + + + + + + \ No newline at end of file diff --git a/TestTINKLib/Tests.cs b/TestTINKLib/Tests.cs new file mode 100644 index 0000000..b4c0814 --- /dev/null +++ b/TestTINKLib/Tests.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; +using System.Linq; +using NUnit.Framework; +using Xamarin.UITest; +using Xamarin.UITest.Queries; + +namespace TestTINKLib +{ + [TestFixture(Platform.Android)] + [TestFixture(Platform.iOS)] + public class Tests + { + IApp app; + Platform platform; + + public Tests(Platform platform) + { + this.platform = platform; + } + + [SetUp] + public void BeforeEachTest() + { + app = AppInitializer.StartApp(platform); + } + + } +} + diff --git a/TestTINKLib/app.config b/TestTINKLib/app.config new file mode 100644 index 0000000..1f3db04 --- /dev/null +++ b/TestTINKLib/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file