async def test_get_schedule3(self):
        """Test that schedule 3 support works."""
        vehicle = Vehicle(conn=TimersConnection(None), url=MOCK_VIN)
        vehicle._discovered = True

        with patch.dict(vehicle._services,
                        {"timerprogramming_v1": {
                            "active": True
                        }}):
            await vehicle.get_timerprogramming()
            self.assertTrue(vehicle.is_departure_timer3_supported)
            self.assertEqual(
                {
                    "timestamp":
                    datetime.fromisoformat("2022-02-22T20:00:22+00:00"),
                    "timerID":
                    "3",
                    "profileID":
                    "1",
                    "timerProgrammedStatus":
                    "notProgrammed",
                    "timerFrequency":
                    "cyclic",
                    "currentCalendarProvider": {},
                    "departureTimeOfDay":
                    "07:55",
                    "departureWeekdayMask":
                    "nnnnnyn",
                },
                vehicle.departure_timer3.__dict__,
            )
 async def test_lock_not_supported(self):
     """Test that remote locking throws exception if not supported."""
     vehicle = Vehicle(conn=None, url="dummy34")
     vehicle._discovered = True
     vehicle._services["rlu_v1"] = {"active": False}
     try:
         await vehicle.set_lock("any", "")
     except Exception as ex:
         self.assertEqual("Remote lock/unlock is not supported.",
                          ex.__str__())
 async def test_is_primary_engine_electric(self):
     """Test primary electric engine."""
     vehicle = Vehicle(conn=None, url="dummy34")
     vehicle._states["StoredVehicleDataResponseParsed"] = {
         P.PRIMARY_DRIVE: {
             "value": ENGINE_TYPE_ELECTRIC
         }
     }
     self.assertTrue(vehicle.is_primary_drive_electric())
     self.assertFalse(vehicle.is_primary_drive_combustion())
    async def test_get_timerprogramming(self):
        """Vehicle with timers loaded."""
        vehicle = Vehicle(conn=TimersConnection(None), url=MOCK_VIN)
        vehicle._discovered = True

        with patch.dict(vehicle._services,
                        {"timerprogramming_v1": {
                            "active": True
                        }}):
            await vehicle.get_timerprogramming()
            self.assertIn("timer", vehicle._states)
            self.assertIsInstance(vehicle._states["timer"], TimerData)
    async def test_get_schedule_no_basic_settings(self):
        """Test that not found schedule is unsupported."""
        vehicle = Vehicle(conn=TimersConnectionNoSettings(None), url=MOCK_VIN)
        vehicle._discovered = True

        with patch.dict(vehicle._services,
                        {"timerprogramming_v1": {
                            "active": True
                        }}):
            await vehicle.get_timerprogramming()
            self.assertFalse(vehicle.is_schedule_supported(42))
            with self.assertRaises(ValueError):
                self.assertIsNone(vehicle.schedule(42))
    async def test_get_schedule1(self):
        """Test that schedule 1 support works."""
        vehicle = Vehicle(conn=TimersConnection(None), url=MOCK_VIN)
        vehicle._discovered = True

        with patch.dict(vehicle._services,
                        {"timerprogramming_v1": {
                            "active": True
                        }}):
            await vehicle.get_timerprogramming()
            self.assertFalse(vehicle.is_departure_timer1_supported)
            with self.assertRaises(ValueError):
                self.assertIsNone(vehicle.departure_timer1)
    async def test_json(self):
        """Test JSON serialization of dict containing datetime."""
        vehicle = Vehicle(conn=None, url="dummy34")

        vehicle._discovered = True
        dtstring = "2022-02-22T02:22:20+02:00"
        d = datetime.fromisoformat(dtstring)

        with patch.dict(vehicle.attrs, {"a string": "yay", "some date": d}):
            res = f"{vehicle.json}"
            self.assertEqual(
                '{\n    "a string": "yay",\n    "some date": "2022-02-22T02:22:20+02:00"\n}',
                res)
    async def test_is_last_connected_supported(self):
        """Test that parsing last connected works."""
        vehicle = Vehicle(conn=None, url="dummy34")

        vehicle._discovered = True

        with patch.dict(vehicle.attrs, {}):
            res = vehicle.is_last_connected_supported
            self.assertFalse(
                res,
                "Last connected supported returned True without attributes.")

        with patch.dict(vehicle.attrs, {"StoredVehicleDataResponse": {}}):
            res = vehicle.is_last_connected_supported
            self.assertFalse(
                res,
                "Last connected supported returned True without 'vehicleData'."
            )

        with patch.dict(vehicle.attrs,
                        {"StoredVehicleDataResponse": {
                            "vehicleData": {}
                        }}):
            res = vehicle.is_last_connected_supported
            self.assertFalse(
                res,
                "Last connected supported returned True without 'vehicleData.data'."
            )

        with patch.dict(
                vehicle.attrs,
            {"StoredVehicleDataResponse": {
                "vehicleData": {
                    "data": []
                }
            }}):
            res = vehicle.is_last_connected_supported
            self.assertFalse(
                res,
                "Last connected supported returned True without 'vehicleData.data[].field[]'."
            )

        # test with a "real" response
        with open(status_report_json_file) as f:
            data = json_loads(f.read())
        with patch.dict(vehicle.attrs, data):
            res = vehicle.is_last_connected_supported
            self.assertTrue(
                res,
                "Last connected supported returned False when it should have been True"
            )
    async def test_has_combustion_engine(self):
        """Test check for ICE."""
        vehicle = Vehicle(conn=None, url="dummy34")
        vehicle._states["StoredVehicleDataResponseParsed"] = {
            P.PRIMARY_DRIVE: {
                "value": ENGINE_TYPE_DIESEL
            },
            P.SECONDARY_DRIVE: {
                "value": ENGINE_TYPE_ELECTRIC
            },
        }
        self.assertTrue(vehicle.has_combustion_engine())

        # not sure if this exists, but :shrug:
        vehicle._states["StoredVehicleDataResponseParsed"] = {
            P.PRIMARY_DRIVE: {
                "value": ENGINE_TYPE_ELECTRIC
            },
            P.SECONDARY_DRIVE: {
                "value": ENGINE_TYPE_GASOLINE
            },
        }
        self.assertTrue(vehicle.has_combustion_engine())

        # not sure if this exists, but :shrug:
        vehicle._states["StoredVehicleDataResponseParsed"] = {
            P.PRIMARY_DRIVE: {
                "value": ENGINE_TYPE_ELECTRIC
            },
            P.SECONDARY_DRIVE: {
                "value": ENGINE_TYPE_ELECTRIC
            },
        }
        self.assertFalse(vehicle.has_combustion_engine())
    def test_timer_serialization_without_basic_settings(self):
        """Test de- and serialization of timers."""
        newdata: dict = copy.deepcopy(self.data["timer"])
        newdata["timersAndProfiles"]["timerBasicSetting"] = None
        timer = TimerData(**newdata)
        self.assertEqual({"timer": newdata}, timer.json)
        self.assertTrue(timer.valid)
        self.assertNotEqual(timer.json, timer.json_updated)

        vehicle = Vehicle(None, "")
        vehicle.attrs["timer"] = timer
        self.assertFalse(vehicle.is_schedule_min_charge_level_supported)
        self.assertFalse(vehicle.is_timer_basic_settings_supported)
        self.assertFalse(vehicle.is_departure_timer2_supported)
        self.assertTrue(vehicle.is_departure_timer3_supported)
    async def test_get_schedule_last_updated(self):
        """Test that not found schedule is unsupported."""
        vehicle = Vehicle(conn=TimersConnection(None), url=MOCK_VIN)
        vehicle._discovered = True

        with patch.dict(vehicle._services,
                        {"timerprogramming_v1": {
                            "active": True
                        }}):
            await vehicle.get_timerprogramming()
            dt = datetime.fromisoformat(
                "2022-02-22T20:00:22+00:00").astimezone(timezone.utc)
            self.assertEqual(dt, vehicle.schedule_heater_source_last_updated)
            self.assertEqual(dt,
                             vehicle.schedule_min_charge_level_last_updated)
            self.assertEqual(dt, vehicle.departure_timer3_last_updated)
    async def test_lock_supported(self):
        """Test that invalid locking action raises exception."""
        vehicle = Vehicle(conn=None, url="dummy34")
        vehicle._discovered = True
        vehicle._services["rlu_v1"] = {"active": True}
        try:
            self.assertFalse(await vehicle.set_lock("any", ""))
        except Exception as ex:
            self.assertEqual(ex.__str__(), "Invalid lock action: any")

        # simulate request in progress
        vehicle._requests["lock"] = {
            "id": "Foo",
            "timestamp": datetime.now() - timedelta(seconds=20)
        }
        self.assertFalse(await vehicle.set_lock("lock", ""))
 async def test_in_progress(self):
     """Test that _in_progress works as expected."""
     vehicle = Vehicle(conn=None, url="dummy34")
     vehicle._requests["timed_out"] = {
         "id": "1",
         "timestamp": datetime.now() - timedelta(minutes=20)
     }
     vehicle._requests["in_progress"] = {
         "id": 2,
         "timestamp": datetime.now() - timedelta(seconds=20)
     }
     vehicle._requests["unknown"] = {"id": "Foo"}
     self.assertFalse(vehicle._in_progress("timed_out"))
     self.assertTrue(vehicle._in_progress("in_progress"))
     self.assertFalse(vehicle._in_progress("not-defined"))
     self.assertTrue(vehicle._in_progress("unknown", 2))
     self.assertFalse(vehicle._in_progress("unknown", 4))
    async def test_last_connected(self):
        """
        Test that parsing last connected works.

        Data in json is: "tsCarSentUtc": "2022-02-14T00:00:45Z",
        and the function returns local time
        """
        vehicle = Vehicle(conn=None, url="dummy34")

        vehicle._discovered = True

        with open(status_report_json_file) as f:
            data = json_loads(f.read())
        with patch.dict(vehicle.attrs, data):
            res = vehicle.last_connected
            self.assertEqual(
                datetime.fromisoformat("2022-02-14T00:00:45+00:00").astimezone(
                    None).strftime("%Y-%m-%d %H:%M:%S"), res)
    def test_requests_remaining(self):
        """Test requests remaining logic."""
        vehicle = Vehicle(conn=None, url="")
        with patch.dict(vehicle._requests, {"remaining": 22}):
            self.assertTrue(vehicle.is_requests_remaining_supported)
            self.assertEqual(22, vehicle.requests_remaining)
        # if remaining is missing _and_ attrs has no rate limit remaining attribute
        with patch.dict(vehicle._requests, {}):
            del vehicle._requests["remaining"]
            self.assertFalse(vehicle.is_requests_remaining_supported)
            with self.assertRaises(KeyError):
                vehicle.requests_remaining()

            # and with the attribute
            with patch.dict(vehicle._states, {"rate_limit_remaining": 99}):
                self.assertEqual(99, vehicle.requests_remaining)
                # attribute should be removed once read
                self.assertNotIn("rate_limit_remaining", vehicle.attrs)
    async def test_update_deactivated(self):
        """Test that calling update on a deactivated Vehicle does nothing."""
        vehicle = MagicMock(spec=Vehicle, name="MockDeactivatedVehicle")
        vehicle.update = lambda: Vehicle.update(vehicle)
        vehicle._discovered = True
        vehicle._deactivated = True

        await vehicle.update()

        vehicle.discover.assert_not_called()
        # Verify that no other methods were called
        self.assertEqual(0, vehicle.method_calls.__len__(),
                         f"xpected none, got {vehicle.method_calls}")
    async def test_is_primary_engine_combustion(self):
        """Test primary ICE."""
        vehicle = Vehicle(conn=None, url="dummy34")
        vehicle._states["StoredVehicleDataResponseParsed"] = {
            P.PRIMARY_DRIVE: {
                "value": ENGINE_TYPE_DIESEL
            },
            P.SECONDARY_DRIVE: {
                "value": ENGINE_TYPE_ELECTRIC
            },
        }
        self.assertTrue(vehicle.is_primary_drive_combustion())
        self.assertFalse(vehicle.is_primary_drive_electric())
        self.assertFalse(vehicle.is_secondary_drive_combustion())
        self.assertTrue(vehicle.is_secondary_drive_electric())

        # No secondary engine
        vehicle._states["StoredVehicleDataResponseParsed"] = {
            P.PRIMARY_DRIVE: {
                "value": ENGINE_TYPE_GASOLINE
            }
        }
        self.assertTrue(vehicle.is_primary_drive_combustion())
        self.assertFalse(vehicle.is_secondary_drive_electric())
    async def test_update(self):
        """Test that update calls the wanted methods and nothing else."""
        vehicle = MagicMock(spec=Vehicle, name="MockUpdateVehicle")
        vehicle.update = lambda: Vehicle.update(vehicle)

        vehicle._discovered = False
        vehicle.deactivated = False
        await vehicle.update()

        vehicle.discover.assert_called_once()
        vehicle.get_preheater.assert_called_once()
        vehicle.get_climater.assert_called_once()
        vehicle.get_trip_statistic.assert_called_once()
        vehicle.get_position.assert_called_once()
        vehicle.get_statusreport.assert_called_once()
        vehicle.get_charger.assert_called_once()
        vehicle.get_timerprogramming.assert_called_once()

        # Verify that only the expected functions above were called
        self.assertEqual(
            8, vehicle.method_calls.__len__(),
            f"Wrong number of methods called. Expected 8, got {vehicle.method_calls}"
        )
    async def test_init(self):
        """Test __init__."""
        async with ClientSession() as conn:
            target_date = datetime.fromisoformat("2022-02-14 03:04:05")
            url = "https://foo.bar"
            vehicle = Vehicle(conn, url)
            self.assertEqual(conn, vehicle._connection)
            self.assertEqual(url, vehicle._url)
            self.assertEqual("https://msg.volkswagen.de", vehicle._homeregion)
            self.assertFalse(vehicle._discovered)
            self.assertEqual({}, vehicle._states)
            self.assertEqual(30, vehicle._climate_duration)
            self.assertDictEqual(
                {
                    "batterycharge": {
                        "status": "",
                        "timestamp": target_date
                    },
                    "climatisation": {
                        "status": "",
                        "timestamp": target_date
                    },
                    "departuretimer": {
                        "status": "",
                        "timestamp": target_date
                    },
                    "latest": "",
                    "lock": {
                        "status": "",
                        "timestamp": target_date
                    },
                    "preheater": {
                        "status": "",
                        "timestamp": target_date
                    },
                    "refresh": {
                        "status": "",
                        "timestamp": target_date
                    },
                    "remaining": -1,
                    "state": "",
                },
                vehicle._requests,
            )

            self.assertDictEqual(
                {
                    "carfinder_v1": {
                        "active": False
                    },
                    "rbatterycharge_v1": {
                        "active": False
                    },
                    "rclima_v1": {
                        "active": False
                    },
                    "rheating_v1": {
                        "active": False
                    },
                    "rhonk_v1": {
                        "active": False
                    },
                    "rlu_v1": {
                        "active": False
                    },
                    "statusreport_v1": {
                        "active": False
                    },
                    "timerprogramming_v1": {
                        "active": False
                    },
                    "trip_statistic_v1": {
                        "active": False
                    },
                },
                vehicle._services,
            )
 def test_requests_remaining_last_updated(self):
     """Test requests remaining logic."""
     vehicle = Vehicle(conn=None, url="")
     vehicle.requests_remaining = 4
     self.assertEqual(datetime.fromisoformat("2022-02-02T02:02:02"),
                      vehicle.requests_remaining_last_updated)
 def test_str(self):
     """Test __str__."""
     vehicle = Vehicle(None, "XYZ1234567890")
     self.assertEqual("XYZ1234567890", vehicle.__str__())