
test_client = <httpx.AsyncClient object at 0x1064ee510>, app = <fastapi.applications.FastAPI object at 0x106280690>, mocker = <pytest_mock.plugin.MockerFixture object at 0x10660a820>
station = ClassicServer(id='yc_station_67', branch_id='', vendor_id='vendor_yc', field_server_id='', station_title='測試站', descri...670': ClassicServerChargerMapItemValue(charger_id='bnl_ocppv16_smart12345670', power_type=None, connector_count=None)})
charger = ClassicCharger(id='bnl_ocppv16_smart12345670', station_id='yc_station_67', charge_station='', name='測試樁')

    @pytest.mark.asyncio
    async def test_available(test_client: AsyncClient, app: FastAPI, mocker: MockerFixture, station: ClassicServer, charger: ClassicCharger):
        _set_auth_override(mocker)
        _set_redis_mock(app, mocker, _make_charger_info())
    
        app_state = cast(AppState, app.state)
        config_db = app_state['classic_config_db_engine']
        poi_db = app_state['classic_poi_db_engine']
    
        await config_db.save(station)
        await poi_db.save(charger)
    
        try:
            _set_fee_mock(mocker, fee=8.0, unit=FeeUnitEnum.FEE_UNIT_BY_KWH)
    
>           response = await test_client.get(f'/classic/chargers/{CHARGER_ID}')
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

app/tests/test_100_classic_app/test_114_charger.py:142: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/httpx/_client.py:1768: in get
    return await self.request(
.venv/lib/python3.13/site-packages/httpx/_client.py:1540: in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:1629: in send
    response = await self._send_handling_auth(
.venv/lib/python3.13/site-packages/httpx/_client.py:1657: in _send_handling_auth
    response = await self._send_handling_redirects(
.venv/lib/python3.13/site-packages/httpx/_client.py:1694: in _send_handling_redirects
    response = await self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:1730: in _send_single_request
    response = await transport.handle_async_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_transports/asgi.py:170: in handle_async_request
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/applications.py:1163: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/applications.py:90: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
.venv/lib/python3.13/site-packages/starlette/middleware/base.py:198: in __call__
    raise app_exc
.venv/lib/python3.13/site-packages/starlette/middleware/base.py:144: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
.venv/lib/python3.13/site-packages/starlette/middleware/cors.py:88: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/gzip.py:29: in __call__
    await responder(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/gzip.py:130: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/gzip.py:46: in __call__
    await self.app(scope, receive, self.send_with_compression)
.venv/lib/python3.13/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:660: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2683: in app
    await route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:448: in handle
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/applications.py:1163: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/applications.py:90: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
.venv/lib/python3.13/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:660: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2683: in app
    await route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1753: in handle
    await self.original_router.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2738: in handle
    await included_router._handle_selected(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1773: in _handle_selected
    await original_route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1264: in handle
    await app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:150: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/routing.py:136: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/routing.py:690: in app
    raw_response = await run_endpoint_function(
.venv/lib/python3.13/site-packages/fastapi/routing.py:344: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

charger_id = 'bnl_ocppv16_smart12345670', auth_result = {'error_code': <ClassicErrorCodeEnum.ERROR_OK: 0>, 'member': <MagicMock id='4401383808'>}
app_state = <starlette.datastructures.State object at 0x105540d70>, connector_id = 1

    @router.get('/{charger_id}', status_code=status.HTTP_200_OK, operation_id='get_charger')
    async def get_charger(
        charger_id: str,
        auth_result: Annotated[AuthMemberResult, Security(get_authed_and_active_member_di)],
        app_state: Annotated[AppState, Depends(get_app_state_di)],
        connector_id: int = Query(1, description='Connector ID'),
    ) -> GetChargerResponse:
        '''
        復刻自 new-home GET /api/chargers/{charge_id}
    
        - 原本 new-home 的 charger ID 在路徑最後一個 segment，這裡採用 path parameter。
        - 成功回應沿用 new-home 慣例之 200。
        - 錯誤也沿用 new-home 慣例之 200，不用 4xx、5xx。
        - 回應之 `site_info.name` 統一命名為格式 {站名}-{樁名}，如果兩個都沒有，就是 unnamed-unnamed。
    
        費率方面：
        - 原本 new-home 有針對 northsungreen 站做特案處理，這裏沒有。
        - 原本 new-home 繞大圈跑層層函式在 Redis 與 DB 抓 smart charging configuration 然後再組一大堆 TOU slot 費率資料，這裏化繁為簡，只從 DB 抓當站當下 smart charging 費率，然後判斷 rate type。
        '''
        auth_result_code = auth_result['error_code']
        if auth_result_code != ClassicErrorCodeEnum.ERROR_OK:
            return GetChargerResponse(code=auth_result_code)
    
        redis_db0_client = app_state['classic_redis_clients']['db0_client']
        try: charger_info_bytes = cast(bytes | None, redis_db0_client.hget(RedisDb0HashNameEnum.CHARGER_INFO.value, charger_id))
        except Exception as e:
            logger.error({'title': 'Read charger info from Redis error', 'charger_id': charger_id, 'exception': repr(e)})
            return GetChargerResponse( # 沿用 new-home 回應，但這不合理，應該是 `ERROR_CHARGER_NO_EXIST` 或 `ERROR_CHARGER_DISCONNECTED` 較合理。
                code=ClassicErrorCodeEnum.ERROR_OK,
                available=False,
                site_info=ChargerSiteInfo(name='unnamed', unit='', rate=0),
            )
    
>       charger_info: dict | None = json.loads(charger_info_bytes.decode()) if charger_info_bytes else None
                                               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       AttributeError: 'str' object has no attribute 'decode'. Did you mean: 'encode'?

app/classic_app/routers/charger_router.py:58: AttributeError
