CI / Formatting (push) Successful in 6s
CI / Linting (push) Successful in 5s
CI / Tests (Python 3.12) (push) Successful in 2m55s
CI / Tests (Python 3.13) (push) Successful in 2m46s
CI / Tests (Python 3.14) (push) Successful in 2m42s
CI / Type Checking (push) Successful in 10s
CI / Spelling (push) Successful in 9s
182 lines
7.2 KiB
Python
182 lines
7.2 KiB
Python
# Copyright 2026 Logan Fick
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Integration tests for ``OwncastClient`` against a live Owncast server."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
|
|
from owlbot.owncast_http import OwncastError
|
|
|
|
if TYPE_CHECKING:
|
|
from owlbot.api.owncast_client import OwncastClient
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
def test_base_url_matches_server(
|
|
owncast_client: OwncastClient, owncast_server: str
|
|
) -> None:
|
|
"""The client's base_url property reflects the server it was built for."""
|
|
assert owncast_client.base_url == owncast_server
|
|
|
|
|
|
class TestRoundTrip:
|
|
"""End-to-end tests whose effect is verified via readback or return value.
|
|
|
|
Either the call is a getter whose return value is the observation, or the
|
|
call is a setter/mutation whose effect is read back within the same test.
|
|
"""
|
|
|
|
async def test_send_message_returns_success_envelope(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""send_message posts a chat message and returns the success envelope."""
|
|
result = await owncast_client.send_message("integration test message")
|
|
assert isinstance(result, str)
|
|
|
|
async def test_send_message_with_unsanitized_true(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""send_message accepts raw HTML when unsanitized=True."""
|
|
result = await owncast_client.send_message("<b>bold</b>", unsanitized=True)
|
|
assert isinstance(result, str)
|
|
|
|
async def test_send_system_message_returns_success_envelope(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""send_system_message posts a system message."""
|
|
result = await owncast_client.send_system_message("system notice")
|
|
assert isinstance(result, str)
|
|
|
|
async def test_send_action_returns_success_envelope(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""send_action posts an action message."""
|
|
result = await owncast_client.send_action("does a test thing")
|
|
assert isinstance(result, str)
|
|
|
|
async def test_send_user_message_always_returns_400(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""send_user_message is deprecated in Owncast v0.2.5 and returns 400.
|
|
|
|
Validates the deprecation contract documented on
|
|
OwncastClient.send_user_message.
|
|
"""
|
|
with pytest.raises(OwncastError) as exc_info:
|
|
await owncast_client.send_user_message()
|
|
assert exc_info.value.status == 400
|
|
|
|
async def test_get_chat_history_is_empty_on_fresh_server(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""get_chat_history returns [] on a fresh server with no sent messages."""
|
|
assert await owncast_client.get_chat_history() == []
|
|
|
|
async def test_get_connected_clients_is_empty_on_fresh_server(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""get_connected_clients returns [] on a fresh server with no viewers."""
|
|
assert await owncast_client.get_connected_clients() == []
|
|
|
|
async def test_set_message_visibility_after_sending(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""set_message_visibility accepts message IDs read from history.
|
|
|
|
Sends a message, reads chat history to obtain its id, hides it, then
|
|
shows it again. Validates the full write-read-mutate round trip.
|
|
"""
|
|
await owncast_client.send_message("visibility-test message")
|
|
history = await owncast_client.get_chat_history()
|
|
assert history, "chat history should contain the just-sent message"
|
|
message_id = str(history[-1]["id"])
|
|
hide_result = await owncast_client.set_message_visibility(
|
|
[message_id], visible=False
|
|
)
|
|
assert isinstance(hide_result, str)
|
|
show_result = await owncast_client.set_message_visibility(
|
|
[message_id], visible=True
|
|
)
|
|
assert isinstance(show_result, str)
|
|
|
|
async def test_get_user_details_for_existing_user(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""get_user_details returns a dict for a user extracted from chat history.
|
|
|
|
The integration bot sends a message, then reads chat history to get
|
|
its own user id, and looks it up. This is the only way to test the
|
|
endpoint end-to-end since Owncast returns 404 for unknown user ids
|
|
(so we can't distinguish "route missing" from "user missing" without
|
|
a real id).
|
|
"""
|
|
await owncast_client.send_message("user-details-test message")
|
|
history = await owncast_client.get_chat_history()
|
|
assert history
|
|
user_id = str(history[-1]["user"]["id"])
|
|
details = await owncast_client.get_user_details(user_id)
|
|
assert isinstance(details, dict)
|
|
|
|
async def test_get_status_returns_dict(self, owncast_client: OwncastClient) -> None:
|
|
"""get_status reports offline on a fresh server.
|
|
|
|
The public ``/api/status`` endpoint does not expose ``viewerCount``
|
|
(that is only surfaced to admin callers). ``serverTime`` is always
|
|
populated but its value is a moving target, so only its presence
|
|
is asserted.
|
|
"""
|
|
status = await owncast_client.get_status()
|
|
assert isinstance(status, dict)
|
|
assert status["online"] is False
|
|
assert "serverTime" in status
|
|
|
|
|
|
class TestSmoke:
|
|
"""Tests that only verify the wrapper reaches the right endpoint.
|
|
|
|
The backing effect is either unobservable from the integration client
|
|
(``get_status`` does not surface the configured stream title) or concerns
|
|
clients that are not connected in a fresh install. Assertions validate
|
|
that the request is routed correctly, not that any effect was applied.
|
|
"""
|
|
|
|
async def test_send_system_message_to_client_accepts_unknown_id(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""send_system_message_to_client returns success for unknown client IDs.
|
|
|
|
A fresh Owncast has no connected clients. Owncast silently accepts
|
|
system messages to unknown client IDs (the message is discarded).
|
|
The assertion only validates the request path reaches the server and
|
|
the envelope parses, not that anyone received the message.
|
|
"""
|
|
result = await owncast_client.send_system_message_to_client(1, "hello")
|
|
assert isinstance(result, str)
|
|
|
|
async def test_set_stream_title_succeeds(
|
|
self, owncast_client: OwncastClient
|
|
) -> None:
|
|
"""set_stream_title returns the success envelope.
|
|
|
|
The /api/status endpoint does not surface streamTitle, so we only
|
|
validate the envelope. Admin-side test covers reading the value back.
|
|
"""
|
|
result = await owncast_client.set_stream_title("Integration Test Stream")
|
|
assert isinstance(result, str)
|