def test_file_store_expired_token() -> None: """Test loading expired token from credential store.""" with tempfile.TemporaryDirectory() as tmpdirname: # Prepare initial store expired_token = get_jwt(timedelta(seconds=-900)) old_filename = f"{tmpdirname}/.credentials/renault-api.json" old_credential_store = FileCredentialStore(old_filename) test_key = "key" test_value = Credential("value") test_jwt_key = "gigya_jwt" test_jwt_value = Credential(expired_token) # bypass JWTCredential old_credential_store[test_key] = test_value old_credential_store[test_jwt_key] = test_jwt_value assert test_key in old_credential_store assert old_credential_store.get(test_key) == test_value assert old_credential_store[test_key] == test_value assert test_jwt_key in old_credential_store assert old_credential_store.get(test_jwt_key) == test_jwt_value assert old_credential_store[test_jwt_key] == test_jwt_value # Copy the data into new file new_filename = f"{tmpdirname}/.credentials/renault-api-copy.json" copyfile(old_filename, new_filename) new_credential_store = FileCredentialStore(new_filename) # Check that the data is in the new store assert test_key in new_credential_store assert new_credential_store.get(test_key) == test_value assert new_credential_store[test_key] == test_value # Except the JWT token which was rejected on load assert test_jwt_key not in new_credential_store
def get_logged_in_credential_store() -> CredentialStore: """Get credential store initialised with Gigya credentials.""" credential_store = CredentialStore() credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(get_jwt()) return credential_store
def test_clear_keys() -> None: """Test clearance of specified keys from credential store.""" credential_store = CredentialStore() test_key = "test" test_permanent_key = "locale" # Try to get value from empty store assert test_key not in credential_store assert test_permanent_key not in credential_store # Set value test_value = Credential("test_value") test_pemanent_value = Credential("test_locale") credential_store[test_key] = test_value credential_store[test_permanent_key] = test_pemanent_value # Try to get values from filled store assert test_key in credential_store assert credential_store[test_key] == test_value assert test_permanent_key in credential_store assert credential_store[test_permanent_key] == test_pemanent_value # Clear the store credential_store.clear_keys([test_permanent_key]) # Try to get values from filled store assert test_key in credential_store assert test_permanent_key not in credential_store
def test_http_get(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_charging_settings(mocked_responses, "single") endpoint = ("/commerce/v1/accounts/{account_id}" "/kamereon/kca/car-adapter" "/v1/cars/{vin}/charging-settings") result = cli_runner.invoke( __main__.main, f"http get {endpoint}", ) assert result.exit_code == 0, result.exception expected_output = ( "{'data': {'type': 'Car', 'id': 'VF1AAAAA555777999', 'attributes': {" "'mode': 'scheduled', 'schedules': [" "{'id': 1, 'activated': True, " "'monday': {'startTime': 'T12:00Z', 'duration': 15}, " "'tuesday': {'startTime': 'T04:30Z', 'duration': 420}, " "'wednesday': {'startTime': 'T22:30Z', 'duration': 420}, " "'thursday': {'startTime': 'T22:00Z', 'duration': 420}, " "'friday': {'startTime': 'T12:15Z', 'duration': 15}, " "'saturday': {'startTime': 'T12:30Z', 'duration': 30}, " "'sunday': {'startTime': 'T12:45Z', 'duration': 45}}" "]}}}\n") assert expected_output == result.output
def test_http_post_file(tmpdir: Any, mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) url = fixtures.inject_set_charge_schedule(mocked_responses, "schedules") endpoint = ("/commerce/v1/accounts/{account_id}" "/kamereon/kca/car-adapter" "/v2/cars/{vin}/actions/charge-schedule") body = { "data": { "type": "ChargeSchedule", "attributes": { "schedules": [] } } } json_file = tmpdir.mkdir("json").join("sample.json") json_file.write(json.dumps(body)) result = cli_runner.invoke( __main__.main, f"http post-file {endpoint} '{json_file}'", ) assert result.exit_code == 0, result.exception expected_output = ( "{'data': {'type': 'ChargeSchedule', 'id': 'guid', " "'attributes': {'schedules': [" "{'id': 1, 'activated': True, " "'tuesday': {'startTime': 'T04:30Z', 'duration': 420}, " "'wednesday': {'startTime': 'T22:30Z', 'duration': 420}, " "'thursday': {'startTime': 'T22:00Z', 'duration': 420}, " "'friday': {'startTime': 'T23:30Z', 'duration': 480}, " "'saturday': {'startTime': 'T18:30Z', 'duration': 120}, " "'sunday': {'startTime': 'T12:45Z', 'duration': 45}}]}}}\n") assert expected_output == result.output expected_json = { "data": { "type": "ChargeSchedule", "attributes": { "schedules": [] } } } request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"]
def initialise_credential_store( include_account_id: Optional[bool] = None, include_vin: Optional[bool] = None, ) -> None: """Initialise CLI credential store.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(get_jwt()) if include_account_id: credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) if include_vin: credential_store[CONF_VIN] = Credential(TEST_VIN)
def test_vehicle_status_no_prompt(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_vehicle_status(mocked_responses) result = cli_runner.invoke( __main__.main, f"--account {TEST_ACCOUNT_ID} --vin {TEST_VIN} status") assert result.exit_code == 0, result.exception assert EXPECTED_BATTERY_STATUS == result.output
async def get_locale(websession: aiohttp.ClientSession, ctx_data: Dict[str, Any]) -> str: """Prompt the user for locale.""" credential_store: CredentialStore = ctx_data["credential_store"] locale = credential_store.get_value(CONF_LOCALE) if locale: return locale default_locale = getdefaultlocale()[0] while True: locale = click.prompt("Please select a locale", default=default_locale) if locale: # pragma: no branch try: await get_api_keys(locale, websession=websession) except RenaultException as exc: # pragma: no cover click.echo(f"Locale `{locale}` is unknown: {exc}", err=True) else: if click.confirm( "Do you want to save the locale to the credential store?", default=False, ): credential_store[CONF_LOCALE] = Credential(locale) # Add blank new line click.echo("") return locale
def test_simple_credential() -> None: """Test get/set with simple credential.""" credential_store = CredentialStore() test_key = "test" # Try to get value from empty store assert test_key not in credential_store assert not credential_store.get(test_key) with pytest.raises(KeyError): credential_store[test_key] # Set value test_value = Credential("test_value") credential_store[test_key] = test_value # Try to get values from filled store assert test_key in credential_store assert credential_store.get(test_key) == test_value assert credential_store[test_key] == test_value # Delete value del credential_store[test_key] # Try to get values from filled store assert test_key not in credential_store assert credential_store.get(test_key) is None assert credential_store.get_value(test_key) is None
def test_file_store() -> None: """Test file credential store.""" with tempfile.TemporaryDirectory() as tmpdirname: # Prepare initial store old_filename = f"{tmpdirname}/.credentials/renault-api.json" old_credential_store = FileCredentialStore(old_filename) test_key = "key" test_value = Credential("value") test_jwt_key = "gigya_jwt" test_jwt_value = JWTCredential(get_jwt()) old_credential_store[test_key] = test_value old_credential_store[test_jwt_key] = test_jwt_value assert test_key in old_credential_store assert old_credential_store.get(test_key) == test_value assert old_credential_store[test_key] == test_value assert test_jwt_key in old_credential_store assert old_credential_store.get(test_jwt_key) == test_jwt_value assert old_credential_store[test_jwt_key] == test_jwt_value # Copy the data into new file new_filename = f"{tmpdirname}/.credentials/renault-api-copy.json" copyfile(old_filename, new_filename) new_credential_store = FileCredentialStore(new_filename) # Check that the data is in the new store assert test_key in new_credential_store assert new_credential_store.get(test_key) == test_value assert new_credential_store[test_key] == test_value assert test_jwt_key in new_credential_store assert new_credential_store.get(test_jwt_key) == test_jwt_value assert new_credential_store[test_jwt_key] == test_jwt_value
def test_list_vehicles_no_prompt(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_vehicles(mocked_responses, "zoe_40.1.json") result = cli_runner.invoke(__main__.main, f"--account {TEST_ACCOUNT_ID} vehicles") assert result.exit_code == 0, result.exception expected_output = ("Registration Brand Model VIN\n" "-------------- ------- ------- -----------------\n" "REG-NUMBER RENAULT ZOE VF1AAAAA555777999\n") assert expected_output == result.output
def test_invalid_credential() -> None: """Test set with invalid types.""" credential_store = CredentialStore() test_key = "test" with pytest.raises(TypeError): credential_store[test_key] = test_key # type:ignore test_value = Credential("test_value") with pytest.raises(TypeError): credential_store[test_value] = test_value # type:ignore
def test_vehicle_status(mocked_responses: aioresponses, cli_runner: CliRunner, filename: str) -> None: """It exits with a status code of zero.""" filename = os.path.basename(filename) credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_vehicle_details(mocked_responses, filename) fixtures.inject_get_vehicle_contracts(mocked_responses, "fr_FR.2.json") fixtures.inject_vehicle_status(mocked_responses) result = cli_runner.invoke(__main__.main, "status") assert result.exit_code == 0, result.exception assert EXPECTED_STATUS[filename] == result.output
async def set_options( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any], locale: Optional[str], account: Optional[str], vin: Optional[str], ) -> None: """Set configuration keys.""" credential_store: CredentialStore = ctx_data["credential_store"] if locale: # Ensure API keys are available api_keys = await get_api_keys(locale, websession=websession) credential_store[CONF_LOCALE] = Credential(locale) for k, v in api_keys.items(): credential_store[k] = Credential(v) if account: credential_store[CONF_ACCOUNT_ID] = Credential(account) if vin: credential_store[CONF_VIN] = Credential(vin)
def test_vehicle_contracts(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_vehicle_contracts(mocked_responses, "fr_FR.1.json") result = cli_runner.invoke(__main__.main, "contracts") assert result.exit_code == 0, result.exception expected_output = ( "Type Code " "Description Start End Status\n" "------------------------------ -------------------- " "-------------------------------- ---------- ---------- " "------------------\n" "WARRANTY_MAINTENANCE_CONTRACTS 40 " "CONTRAT LOSANGE 2018-04-04 2022-04-03 Actif\n" "CONNECTED_SERVICES ZECONNECTP " "My Z.E. Connect en série 36 mois 2018-08-23 2021-08-23 Actif\n" "CONNECTED_SERVICES GBA " "Battery Services 2018-03-23 " "Echec d’activation\n" "WARRANTY ManufacturerWarranty " "Garantie fabricant 2020-04-03 Expiré\n" "WARRANTY PaintingWarranty " "Garantie peinture 2021-04-03 Actif\n" "WARRANTY CorrosionWarranty " "Garantie corrosion 2030-04-03 Actif\n" "WARRANTY GMPeWarranty " "Garantie GMPe 2020-04-03 Expiré\n" "WARRANTY AssistanceWarranty " "Garantie assistance 2020-04-03 Expiré\n") assert expected_output == result.output
def test_list_accounts_no_prompt( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_person(mocked_responses) result = cli_runner.invoke(__main__.main, "accounts") assert result.exit_code == 0, result.exception expected_output = ( "Type ID\n" "--------- ------------\n" "MYRENAULT account-id-1\n" "SFDC account-id-2\n" ) assert expected_output == result.output
def _read(self) -> None: """Read data from store location.""" if not os.path.exists(self._store_location): return with open(self._store_location) as json_file: data = json.load(json_file) for key, value in data.items(): if key == "gigya_jwt": try: self[key] = JWTCredential(value) except jwt.ExpiredSignatureError: # pragma: no cover pass else: self[key] = Credential(value)
async def _get_account_id(ctx_data: Dict[str, Any], client: RenaultClient) -> str: """Prompt the user for account.""" # First, check context data if "account" in ctx_data: return str(ctx_data["account"]) # Second, check credential store credential_store: CredentialStore = ctx_data["credential_store"] account_id = credential_store.get_value(renault_settings.CONF_ACCOUNT_ID) if account_id: return account_id # Third, prompt the user response = await client.get_person() if not response.accounts: # pragma: no cover raise RenaultException("No account found.") prompt, default = await _get_account_prompt(response.accounts, client) while True: i = int( click.prompt( prompt, default=default, type=click.IntRange(min=1, max=len(response.accounts)), ) ) try: account_id = str(response.accounts[i - 1].accountId) except (KeyError, IndexError) as exc: # pragma: no cover click.echo(f"Invalid option: {exc}.", err=True) else: if click.confirm( # pragma: no branch "Do you want to save the account ID to the credential store?", default=False, ): credential_store[renault_settings.CONF_ACCOUNT_ID] = Credential( account_id ) # Add blank new line click.echo("") return account_id
async def _get_vin(ctx_data: Dict[str, Any], account: RenaultAccount) -> str: """Prompt the user for vin.""" # First, check context data if "vin" in ctx_data: return str(ctx_data["vin"]) # Second, check credential store credential_store: CredentialStore = ctx_data["credential_store"] vin = credential_store.get_value(renault_settings.CONF_VIN) if vin: return vin # Third, prompt the user response = await account.get_vehicles() if not response.vehicleLinks: # pragma: no cover raise RenaultException("No vehicle found.") prompt, default = await _get_vehicle_prompt(response.vehicleLinks, account) while True: i = int( click.prompt( prompt, default=default, type=click.IntRange(min=1, max=len(response.vehicleLinks)), ) ) try: vin = str(response.vehicleLinks[i - 1].vin) except (KeyError, IndexError) as exc: # pragma: no cover click.echo(f"Invalid option: {exc}.", err=True) else: if click.confirm( # pragma: no branch "Do you want to save the VIN to the credential store?", default=False, ): credential_store[renault_settings.CONF_VIN] = Credential(vin) click.echo("") return vin
def test_simple_credential() -> None: """Test for Credential class.""" credential = Credential(TEST_VALUE) assert credential.value == TEST_VALUE assert not credential.has_expired()