async def mock_supervisor_fixture(hass, aioclient_mock):
    """Mock supervisor."""
    aioclient_mock.post("http://127.0.0.1/homeassistant/options",
                        json={"result": "ok"})
    aioclient_mock.post("http://127.0.0.1/supervisor/options",
                        json={"result": "ok"})
    with patch.dict(os.environ, {"HASSIO": "127.0.0.1"}), patch(
            "homeassistant.components.hassio.HassIO.is_connected",
            return_value=True,
    ), patch(
            "homeassistant.components.hassio.HassIO.get_info",
            return_value={},
    ), patch(
            "homeassistant.components.hassio.HassIO.get_host_info",
            return_value={},
    ), patch(
            "homeassistant.components.hassio.HassIO.get_supervisor_info",
            return_value={},
    ), patch(
            "homeassistant.components.hassio.HassIO.get_os_info",
            return_value={},
    ), patch(
            "homeassistant.components.hassio.HassIO.get_ingress_panels",
            return_value={"panels": {}},
    ), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}):
        yield
Exemple #2
0
async def test_load_hassio(hass):
    """Test that we load Hass.io component."""
    with patch.dict(os.environ, {}, clear=True):
        assert bootstrap._get_domains(hass, {}) == set()

    with patch.dict(os.environ, {"HASSIO": "1"}):
        assert bootstrap._get_domains(hass, {}) == {"hassio"}
async def test_two_step_flow(hass, client):
    """Test we can finish a two step flow."""
    mock_integration(
        hass, MockModule("test",
                         async_setup_entry=AsyncMock(return_value=True)))
    mock_entity_platform(hass, "config_flow.test", None)

    class TestFlow(core_ce.ConfigFlow):
        VERSION = 1

        async def async_step_user(self, user_input=None):
            return self.async_show_form(step_id="account",
                                        data_schema=vol.Schema(
                                            {"user_title": str}))

        async def async_step_account(self, user_input=None):
            return self.async_create_entry(title=user_input["user_title"],
                                           data={"secret": "account_token"})

    with patch.dict(HANDLERS, {"test": TestFlow}):
        resp = await client.post("/api/config/config_entries/flow",
                                 json={"handler": "test"})
        assert resp.status == 200
        data = await resp.json()
        flow_id = data.pop("flow_id")
        assert data == {
            "type": "form",
            "handler": "test",
            "step_id": "account",
            "data_schema": [{
                "name": "user_title",
                "type": "string"
            }],
            "description_placeholders": None,
            "errors": None,
        }

    with patch.dict(HANDLERS, {"test": TestFlow}):
        resp = await client.post(
            f"/api/config/config_entries/flow/{flow_id}",
            json={"user_title": "user-title"},
        )
        assert resp.status == 200

        entries = hass.config_entries.async_entries("test")
        assert len(entries) == 1

        data = await resp.json()
        data.pop("flow_id")
        assert data == {
            "handler": "test",
            "type": "create_entry",
            "title": "user-title",
            "version": 1,
            "result": entries[0].entry_id,
            "description": None,
            "description_placeholders": None,
        }
def hassio_env_fixture():
    """Fixture to inject hassio env."""
    with patch.dict(os.environ, {"HASSIO": "127.0.0.1"}), patch(
            "homeassistant.components.hassio.HassIO.is_connected",
            return_value={
                "result": "ok",
                "data": {}
            },
    ), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}):
        yield
async def test_setup_hassio_no_additional_data(hass, aioclient_mock):
    """Test setup with API push default data."""
    with patch.dict(os.environ, MOCK_ENVIRON), patch.dict(
        os.environ, {"HASSIO_TOKEN": "123456"}
    ):
        result = await async_setup_component(hass, "hassio", {"hassio": {}})
        assert result

    assert aioclient_mock.call_count == 6
    assert aioclient_mock.mock_calls[-1][3]["X-Hassio-Key"] == "123456"
def hassio_env():
    """Fixture to inject hassio env."""
    with patch.dict(os.environ, {"HASSIO": "127.0.0.1"}), patch(
        "homeassistant.components.hassio.HassIO.is_connected",
        return_value={"result": "ok", "data": {}},
    ), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}), patch(
        "homeassistant.components.hassio.HassIO.get_info",
        Mock(side_effect=HassioAPIError()),
    ):
        yield
Exemple #7
0
async def test_websocket_status(hass, hass_ws_client, mock_cloud_fixture,
                                mock_cloud_login):
    """Test querying the status."""
    hass.data[DOMAIN].iot.state = STATE_CONNECTED
    client = await hass_ws_client(hass)

    with patch.dict(
            "homeassistant.components.google_assistant.const.DOMAIN_TO_GOOGLE_TYPES",
        {"light": None},
            clear=True,
    ), patch.dict(
            "homeassistant.components.alexa.entities.ENTITY_ADAPTERS",
        {"switch": None},
            clear=True,
    ):
        await client.send_json({"id": 5, "type": "cloud/status"})
        response = await client.receive_json()
    assert response["result"] == {
        "logged_in": True,
        "email": "*****@*****.**",
        "cloud": "connected",
        "prefs": {
            "alexa_enabled": True,
            "cloudhooks": {},
            "google_enabled": True,
            "google_entity_configs": {},
            "google_secure_devices_pin": None,
            "google_default_expose": None,
            "alexa_default_expose": None,
            "alexa_entity_configs": {},
            "alexa_report_state": False,
            "google_report_state": False,
            "remote_enabled": False,
        },
        "alexa_entities": {
            "include_domains": [],
            "include_entity_globs": [],
            "include_entities": ["light.kitchen", "switch.ac"],
            "exclude_domains": [],
            "exclude_entity_globs": [],
            "exclude_entities": [],
        },
        "google_entities": {
            "include_domains": ["light"],
            "include_entity_globs": [],
            "include_entities": [],
            "exclude_domains": [],
            "exclude_entity_globs": [],
            "exclude_entities": [],
        },
        "remote_domain": None,
        "remote_connected": False,
        "remote_certificate": None,
    }
async def test_homekit_match_full(hass, mock_zeroconf):
    """Test configured options for a device are loaded via config entry."""
    with patch.dict(
            zc_gen.ZEROCONF, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]},
            clear=True), patch.object(
                hass.config_entries.flow,
                "async_init") as mock_config_flow, patch.object(
                    zeroconf,
                    "HaServiceBrowser",
                    side_effect=service_update_mock) as mock_service_browser:
        mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock(
            "BSB002", HOMEKIT_STATUS_UNPAIRED)
        assert await async_setup_component(hass, zeroconf.DOMAIN,
                                           {zeroconf.DOMAIN: {}})
        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()

    homekit_mock = get_homekit_info_mock("BSB002", HOMEKIT_STATUS_UNPAIRED)
    info = homekit_mock("_hap._tcp.local.", "BSB002._hap._tcp.local.")
    import pprint

    pprint.pprint(["homekit", info])
    assert len(mock_service_browser.mock_calls) == 1
    assert len(mock_config_flow.mock_calls) == 1
    assert mock_config_flow.mock_calls[0][1][0] == "hue"
Exemple #9
0
async def test_cloud_calling_handler(mock_iot_client, cloud_mock_iot):
    """Test we call handle message with correct info."""
    conn = iot.CloudIoT(cloud_mock_iot)
    mock_iot_client.receive = AsyncMock(
        return_value=MagicMock(
            type=WSMsgType.text,
            json=MagicMock(
                return_value={
                    "msgid": "test-msg-id",
                    "handler": "test-handler",
                    "payload": "test-payload",
                }
            ),
        )
    )
    mock_handler = AsyncMock(return_value="response")
    mock_iot_client.send_json = AsyncMock()

    with patch.dict(iot.HANDLERS, {"test-handler": mock_handler}, clear=True):
        await conn.connect()
        await asyncio.sleep(0)

    # Check that we sent message to handler correctly
    assert len(mock_handler.mock_calls) == 1
    cloud, payload = mock_handler.mock_calls[0][1]

    assert cloud is cloud_mock_iot
    assert payload == "test-payload"

    # Check that we forwarded response from handler to cloud
    assert len(mock_iot_client.send_json.mock_calls) == 1
    assert mock_iot_client.send_json.mock_calls[0][1][0] == {
        "msgid": "test-msg-id",
        "payload": "response",
    }
Exemple #10
0
async def test_setup_api_push_api_data_default(hass, aioclient_mock,
                                               hass_storage):
    """Test setup with API push default data."""
    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(hass, "hassio", {
            "http": {},
            "hassio": {}
        })
        assert result

    assert aioclient_mock.call_count == 6
    assert not aioclient_mock.mock_calls[1][2]["ssl"]
    assert aioclient_mock.mock_calls[1][2]["port"] == 8123
    refresh_token = aioclient_mock.mock_calls[1][2]["refresh_token"]
    hassio_user = await hass.auth.async_get_user(
        hass_storage[STORAGE_KEY]["data"]["hassio_user"])
    assert hassio_user is not None
    assert hassio_user.system_generated
    assert len(hassio_user.groups) == 1
    assert hassio_user.groups[0].id == GROUP_ID_ADMIN
    for token in hassio_user.refresh_tokens.values():
        if token.token == refresh_token:
            break
    else:
        assert False, "refresh token not found"
async def test_get_progress_flow_unauth(hass, client, hass_admin_user):
    """Test we can can't query the API for result of flow."""
    mock_entity_platform(hass, "config_flow.test", None)

    class TestFlow(core_ce.ConfigFlow):
        async def async_step_user(self, user_input=None):
            schema = OrderedDict()
            schema[vol.Required("username")] = str
            schema[vol.Required("password")] = str

            return self.async_show_form(
                step_id="user",
                data_schema=schema,
                errors={"username": "******"},
            )

    with patch.dict(HANDLERS, {"test": TestFlow}):
        resp = await client.post("/api/config/config_entries/flow",
                                 json={"handler": "test"})

    assert resp.status == 200
    data = await resp.json()

    hass_admin_user.groups = []

    resp2 = await client.get("/api/config/config_entries/flow/{}".format(
        data["flow_id"]))

    assert resp2.status == 401
async def test_get_progress_index(hass, hass_ws_client):
    """Test querying for the flows that are in progress."""
    assert await async_setup_component(hass, "config", {})
    mock_entity_platform(hass, "config_flow.test", None)
    ws_client = await hass_ws_client(hass)

    class TestFlow(core_ce.ConfigFlow):
        VERSION = 5

        async def async_step_hassio(self, info):
            return await self.async_step_account()

        async def async_step_account(self, user_input=None):
            return self.async_show_form(step_id="account")

    with patch.dict(HANDLERS, {"test": TestFlow}):
        form = await hass.config_entries.flow.async_init(
            "test", context={"source": "hassio"})

    await ws_client.send_json({
        "id": 5,
        "type": "config_entries/flow/progress"
    })
    response = await ws_client.receive_json()

    assert response["success"]
    assert response["result"] == [{
        "flow_id": form["flow_id"],
        "handler": "test",
        "step_id": "account",
        "context": {
            "source": "hassio"
        },
    }]
async def test_create_account(hass, client):
    """Test a flow that creates an account."""
    mock_entity_platform(hass, "config_flow.test", None)

    mock_integration(
        hass, MockModule("test",
                         async_setup_entry=AsyncMock(return_value=True)))

    class TestFlow(core_ce.ConfigFlow):
        VERSION = 1

        async def async_step_user(self, user_input=None):
            return self.async_create_entry(title="Test Entry",
                                           data={"secret": "account_token"})

    with patch.dict(HANDLERS, {"test": TestFlow}):
        resp = await client.post("/api/config/config_entries/flow",
                                 json={"handler": "test"})

    assert resp.status == 200

    entries = hass.config_entries.async_entries("test")
    assert len(entries) == 1

    data = await resp.json()
    data.pop("flow_id")
    assert data == {
        "handler": "test",
        "title": "Test Entry",
        "type": "create_entry",
        "version": 1,
        "result": entries[0].entry_id,
        "description": None,
        "description_placeholders": None,
    }
async def test_mqtt_discovery_unsubscribe_once(hass, mqtt_client_mock,
                                               mqtt_mock):
    """Check MQTT integration discovery unsubscribe once."""
    mock_entity_platform(hass, "config_flow.comp", None)

    entry = hass.config_entries.async_entries("mqtt")[0]
    mqtt_mock().connected = True

    with patch(
            "homeassistant.components.mqtt.discovery.async_get_mqtt",
            return_value={"comp": ["comp/discovery/#"]},
    ):
        await async_start(hass, "homeassistant", entry)
        await hass.async_block_till_done()

    mqtt_client_mock.subscribe.assert_any_call("comp/discovery/#", 0)
    assert not mqtt_client_mock.unsubscribe.called

    class TestFlow(config_entries.ConfigFlow):
        """Test flow."""
        async def async_step_mqtt(self, discovery_info):
            """Test mqtt step."""
            return self.async_abort(reason="already_configured")

    with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
        async_fire_mqtt_message(hass, "comp/discovery/bla/config", "")
        async_fire_mqtt_message(hass, "comp/discovery/bla/config", "")
        await hass.async_block_till_done()
        await hass.async_block_till_done()
        mqtt_client_mock.unsubscribe.assert_called_once_with(
            "comp/discovery/#")
async def test_access_from_supervisor_ip(remote_addr, bans, status, hass,
                                         aiohttp_client, hassio_env):
    """Test accessing to server from supervisor IP."""
    app = web.Application()
    app["hass"] = hass

    async def unauth_handler(request):
        """Return a mock web response."""
        raise HTTPUnauthorized

    app.router.add_get("/", unauth_handler)
    setup_bans(hass, app, 1)
    mock_real_ip(app)(remote_addr)

    with patch("homeassistant.components.http.ban.async_load_ip_bans_config",
               return_value=[]):
        client = await aiohttp_client(app)

    assert await async_setup_component(hass, "hassio", {"hassio": {}})

    m_open = mock_open()

    with patch.dict(os.environ, {"SUPERVISOR": SUPERVISOR_IP}), patch(
            "homeassistant.components.http.ban.open", m_open, create=True):
        resp = await client.get("/")
        assert resp.status == 401
        assert len(app[KEY_BANNED_IPS]) == bans
        assert m_open.call_count == bans

        # second request should be forbidden if banned
        resp = await client.get("/")
        assert resp.status == status
        assert len(app[KEY_BANNED_IPS]) == bans
Exemple #16
0
async def test_zeroconf_no_match(hass, mock_zeroconf):
    """Test configured options for a device are loaded via config entry."""

    def http_only_service_update_mock(zeroconf, services, handlers):
        """Call service update handler."""
        handlers[0](
            zeroconf,
            "_http._tcp.local.",
            "somethingelse._http._tcp.local.",
            ServiceStateChange.Added,
        )

    with patch.dict(
        zc_gen.ZEROCONF,
        {"_http._tcp.local.": [{"domain": "shelly", "name": "shelly*"}]},
        clear=True,
    ), patch.object(
        hass.config_entries.flow, "async_init"
    ) as mock_config_flow, patch.object(
        zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock
    ) as mock_service_browser:
        mock_zeroconf.get_service_info.side_effect = get_zeroconf_info_mock(
            "FFAADDCC11DD"
        )
        assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()

    assert len(mock_service_browser.mock_calls) == 1
    assert len(mock_config_flow.mock_calls) == 0
Exemple #17
0
async def test_connection_msg_for_handler_raising(mock_iot_client, cloud_mock_iot):
    """Test we sent error when handler raises exception."""
    conn = iot.CloudIoT(cloud_mock_iot)
    mock_iot_client.receive = AsyncMock(
        return_value=MagicMock(
            type=WSMsgType.text,
            json=MagicMock(
                return_value={
                    "msgid": "test-msg-id",
                    "handler": "test-handler",
                    "payload": "test-payload",
                }
            ),
        )
    )
    mock_iot_client.send_json = AsyncMock()

    with patch.dict(
        iot.HANDLERS, {"test-handler": Mock(side_effect=Exception("Broken"))}
    ):
        await conn.connect()
        await asyncio.sleep(0)

    # Check that we sent the correct error
    assert len(mock_iot_client.send_json.mock_calls) == 1
    assert mock_iot_client.send_json.mock_calls[0][1][0] == {
        "msgid": "test-msg-id",
        "error": "exception",
    }
Exemple #18
0
def test_type_camera(type_name, entity_id, state, attrs):
    """Test if camera types are associated correctly."""
    mock_type = Mock()
    with patch.dict(TYPES, {type_name: mock_type}):
        entity_state = State(entity_id, state, attrs)
        get_accessory(None, None, entity_state, 2, {})
    assert mock_type.called
async def test_hassio_system_health_with_issues(hass, aioclient_mock):
    """Test hassio system health."""
    aioclient_mock.get("http://127.0.0.1/info",
                       json={
                           "result": "ok",
                           "data": {}
                       })
    aioclient_mock.get("http://127.0.0.1/host/info",
                       json={
                           "result": "ok",
                           "data": {}
                       })
    aioclient_mock.get("http://127.0.0.1/os/info",
                       json={
                           "result": "ok",
                           "data": {}
                       })
    aioclient_mock.get("http://127.0.0.1/supervisor/ping", text="")
    aioclient_mock.get("https://version.home-assistant.io/stable.json",
                       exc=ClientError)
    aioclient_mock.get("http://127.0.0.1/supervisor/info",
                       json={
                           "result": "ok",
                           "data": {}
                       })

    hass.config.components.add("hassio")
    with patch.dict(os.environ, MOCK_ENVIRON):
        assert await async_setup_component(hass, "system_health", {})

    hass.data["hassio_info"] = {"channel": "stable"}
    hass.data["hassio_host_info"] = {}
    hass.data["hassio_os_info"] = {}
    hass.data["hassio_supervisor_info"] = {
        "healthy": False,
        "supported": False,
    }

    info = await get_system_health_info(hass, "hassio")

    for key, val in info.items():
        if asyncio.iscoroutine(val):
            info[key] = await val

    assert info["healthy"] == {
        "error": "Unhealthy",
        "more_info": "/hassio/system",
        "type": "failed",
    }
    assert info["supported"] == {
        "error": "Unsupported",
        "more_info": "/hassio/system",
        "type": "failed",
    }
    assert info["version_api"] == {
        "error": "unreachable",
        "more_info": "/hassio/system",
        "type": "failed",
    }
Exemple #20
0
def webhook_flow_conf(hass):
    """Register a handler."""
    with patch.dict(config_entries.HANDLERS):
        config_entry_flow.register_webhook_flow("test_single", "Test Single", {}, False)
        config_entry_flow.register_webhook_flow(
            "test_multiple", "Test Multiple", {}, True
        )
        yield {}
Exemple #21
0
def mock_handlers():
    """Mock config flows."""
    class MockFlowHandler(config_entries.ConfigFlow):
        """Define a mock flow handler."""

        VERSION = 1

    with patch.dict(config_entries.HANDLERS, {"comp": MockFlowHandler}):
        yield
async def test_get_entries(hass, client):
    """Test get entries."""
    with patch.dict(HANDLERS, clear=True):

        @HANDLERS.register("comp1")
        class Comp1ConfigFlow:
            """Config flow with options flow."""
            @staticmethod
            @callback
            def async_get_options_flow(config, options):
                """Get options flow."""
                pass

        hass.helpers.config_entry_flow.register_discovery_flow(
            "comp2", "Comp 2", lambda: None, core_ce.CONN_CLASS_ASSUMED)

        entry = MockConfigEntry(
            domain="comp1",
            title="Test 1",
            source="bla",
            connection_class=core_ce.CONN_CLASS_LOCAL_POLL,
        )
        entry.supports_unload = True
        entry.add_to_hass(hass)
        MockConfigEntry(
            domain="comp2",
            title="Test 2",
            source="bla2",
            state=core_ce.ENTRY_STATE_LOADED,
            connection_class=core_ce.CONN_CLASS_ASSUMED,
        ).add_to_hass(hass)

        resp = await client.get("/api/config/config_entries/entry")
        assert resp.status == 200
        data = await resp.json()
        for entry in data:
            entry.pop("entry_id")
        assert data == [
            {
                "domain": "comp1",
                "title": "Test 1",
                "source": "bla",
                "state": "not_loaded",
                "connection_class": "local_poll",
                "supports_options": True,
                "supports_unload": True,
            },
            {
                "domain": "comp2",
                "title": "Test 2",
                "source": "bla2",
                "state": "loaded",
                "connection_class": "assumed",
                "supports_options": False,
                "supports_unload": False,
            },
        ]
async def test_setup_api_ping(hass, aioclient_mock):
    """Test setup with API ping."""
    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(hass, "hassio", {})
        assert result

    assert aioclient_mock.call_count == 6
    assert hass.components.hassio.get_homeassistant_version() == "0.110.0"
    assert hass.components.hassio.is_hassio()
Exemple #24
0
async def test_setup_api_ping(hass, aioclient_mock):
    """Test setup with API ping."""
    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(hass, "hassio", {})
        assert result

    assert aioclient_mock.call_count == 9
    assert hass.components.hassio.get_core_info()["version_latest"] == "1.0.0"
    assert hass.components.hassio.is_hassio()
Exemple #25
0
def test_customize_options(config, name):
    """Test with customized options."""
    mock_type = Mock()
    conf = config.copy()
    conf[ATTR_INTERGRATION] = "platform_name"
    with patch.dict(TYPES, {"Light": mock_type}):
        entity_state = State("light.demo", "on")
        get_accessory(None, None, entity_state, 2, conf)
    mock_type.assert_called_with(None, None, name, "light.demo", 2, conf)
Exemple #26
0
def setup_comp():
    """Set up things to be run when tests are started."""
    _base_mock = MagicMock()
    pykira = _base_mock.pykira
    pykira.__file__ = "test"
    _module_patcher = patch.dict("sys.modules", {"pykira": pykira})
    _module_patcher.start()
    yield
    _module_patcher.stop()
Exemple #27
0
def test_pysqlite_load_failure(stdlib_version, pysqlite3_version):
    """
    Test that the internal import SQLite helper will throw an error when no compatible
    module can be found.
    """

    if pysqlite3_version is not None:
        pysqlite3 = MagicMock()
        pysqlite3.sqlite_version_info = pysqlite3_version
        pysqlite3_patch = patch.dict(sys.modules, {"pysqlite3": pysqlite3})
    else:
        pysqlite3_patch = patch.dict(sys.modules, {"pysqlite3": None})

    with pysqlite3_patch, patch.object(
        sys.modules["sqlite3"], "sqlite_version_info", new=stdlib_version
    ):
        with pytest.raises(RuntimeError):
            zigpy.appdb._import_compatible_sqlite3(zigpy.appdb.MIN_SQLITE_VERSION)
Exemple #28
0
def hassio_handler(hass, aioclient_mock):
    """Create mock hassio handler."""
    async def get_client_session():
        return hass.helpers.aiohttp_client.async_get_clientsession()

    websession = hass.loop.run_until_complete(get_client_session())

    with patch.dict(os.environ, {"HASSIO_TOKEN": HASSIO_TOKEN}):
        yield HassIO(hass.loop, websession, "127.0.0.1")
Exemple #29
0
def test_type_media_player(type_name, entity_id, state, attrs, config):
    """Test if media_player types are associated correctly."""
    mock_type = Mock()
    with patch.dict(TYPES, {type_name: mock_type}):
        entity_state = State(entity_id, state, attrs)
        get_accessory(None, None, entity_state, 2, config)
    assert mock_type.called

    if config:
        assert mock_type.call_args[0][-1] == config
async def test_warn_when_cannot_connect(hass, caplog):
    """Fail warn when we cannot connect."""
    with patch.dict(os.environ, MOCK_ENVIRON), patch(
        "homeassistant.components.hassio.HassIO.is_connected", return_value=None,
    ):
        result = await async_setup_component(hass, "hassio", {})
        assert result

    assert hass.components.hassio.is_hassio()
    assert "Not connected with Hass.io / system to busy!" in caplog.text