Bumps [authlib](https://github.com/authlib/authlib) from 1.3.0 to 1.6.5. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/authlib/authlib/releases">authlib's releases</a>.</em></p> <blockquote> <h2>v1.6.5</h2> <h2>What's Changed</h2> <ul> <li>Add a <code>request</code> param to RFC7591 <code>generate_client_info</code> and <code>generate_client_secret</code> methods by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/825">authlib/authlib#825</a></li> <li>feat: support list params in prepare_grant_uri by <a href="https://github.com/lisongmin"><code>@lisongmin</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/827">authlib/authlib#827</a></li> <li>chore(deps): bump SonarSource/sonarqube-scan-action from 5 to 6 in /.github/workflows by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/authlib/authlib/pull/828">authlib/authlib#828</a></li> <li>fix(jose): add max size for JWE zip=DEF decompression by <a href="https://github.com/lepture"><code>@lepture</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/830">authlib/authlib#830</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/lisongmin"><code>@lisongmin</code></a> made their first contribution in <a href="https://redirect.github.com/authlib/authlib/pull/827">authlib/authlib#827</a></li> <li><a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] made their first contribution in <a href="https://redirect.github.com/authlib/authlib/pull/828">authlib/authlib#828</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/authlib/authlib/compare/v1.6.4...v1.6.5">https://github.com/authlib/authlib/compare/v1.6.4...v1.6.5</a></p> <h2>v1.6.4</h2> <h2>What's Changed</h2> <ul> <li>fix(jose): prevent public/unprotected header overwriting protected header by <a href="https://github.com/lepture"><code>@lepture</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/809">authlib/authlib#809</a></li> <li>Fix <code>InsecureTransportError</code> raising by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/810">authlib/authlib#810</a></li> <li>Add conventional-commits pre-commit hook by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/811">authlib/authlib#811</a></li> <li>Fix response_mode=form_post with Starlette client by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/812">authlib/authlib#812</a></li> <li>Specify README.md as project long description by <a href="https://github.com/EpicWink"><code>@EpicWink</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/817">authlib/authlib#817</a></li> <li>Migrate tests to pytest paradigm by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/813">authlib/authlib#813</a></li> <li>jose/jws: Reject unprotected ‘crit’ and enforce type; add tests by <a href="https://github.com/AL-Cybision"><code>@AL-Cybision</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/823">authlib/authlib#823</a></li> <li>Use explicit *.test urls in unit tests by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/824">authlib/authlib#824</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/EpicWink"><code>@EpicWink</code></a> made their first contribution in <a href="https://redirect.github.com/authlib/authlib/pull/817">authlib/authlib#817</a></li> <li><a href="https://github.com/AL-Cybision"><code>@AL-Cybision</code></a> made their first contribution in <a href="https://redirect.github.com/authlib/authlib/pull/823">authlib/authlib#823</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/authlib/authlib/compare/v1.6.3...v1.6.4">https://github.com/authlib/authlib/compare/v1.6.3...v1.6.4</a></p> <h2>Version 1.6.3</h2> <h2>What's Changed</h2> <ul> <li>Add diff-cover check in GHA by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/803">authlib/authlib#803</a></li> <li>Run GHA unit tests with uv by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/805">authlib/authlib#805</a></li> <li>Move from pre-commit to prek by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/804">authlib/authlib#804</a></li> <li>Sign OIDC id_token according to <code>id_token_signed_response_alg</code> client metadata by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/802">authlib/authlib#802</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/authlib/authlib/compare/v1.6.2...v1.6.3">https://github.com/authlib/authlib/compare/v1.6.2...v1.6.3</a></p> <h2>Version 1.6.2</h2> <h2>What's Changed</h2> <ul> <li>Allow insecure transport for 127.0.0.1 for debugging by <a href="https://github.com/geigerzaehler"><code>@geigerzaehler</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/788">authlib/authlib#788</a></li> <li>Raise a MissingCodeError when code parameter is missing by <a href="https://github.com/lepture"><code>@lepture</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/786">authlib/authlib#786</a></li> <li>Temporarily restore OAuth2Request body parameter by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/791">authlib/authlib#791</a></li> <li>Raise MissingCodeException when code parameter is missing by <a href="https://github.com/lepture"><code>@lepture</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/794">authlib/authlib#794</a></li> <li>Fix id_token generation with EdDSA alg by <a href="https://github.com/azmeuk"><code>@azmeuk</code></a> in <a href="https://redirect.github.com/authlib/authlib/pull/800">authlib/authlib#800</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/authlib/authlib/compare/v1.6.1...v1.6.2">https://github.com/authlib/authlib/compare/v1.6.1...v1.6.2</a></p> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/authlib/authlib/blob/main/docs/changelog.rst">authlib's changelog</a>.</em></p> <blockquote> <h2>Version 1.6.5</h2> <p><strong>Released on Oct 2, 2025</strong></p> <ul> <li>RFC7591 <code>generate_client_info</code> and <code>generate_client_secret</code> take a <code>request</code> parameter.</li> <li>Add size limitation when decode JWS/JWE to prevent DoS.</li> <li>Add size limitation for <code>DEF</code> JWE zip algorithm.</li> </ul> <h2>Version 1.6.4</h2> <p><strong>Released on Sep 17, 2025</strong></p> <ul> <li>Fix <code>InsecureTransportError</code> error raising. :issue:<code>795</code></li> <li>Fix <code>response_mode=form_post</code> with Starlette client. :issue:<code>793</code></li> <li>Validate <code>crit</code> header value, reject unprotected header in <code>crit</code> header.</li> </ul> <h2>Version 1.6.3</h2> <p><strong>Released on Aug 26, 2025</strong></p> <ul> <li>OIDC <code>id_token</code> are signed according to <code>id_token_signed_response_alg</code> client metadata. :issue:<code>755</code></li> </ul> <h2>Version 1.6.2</h2> <p><strong>Released on Aug 23, 2025</strong></p> <ul> <li>Temporarily restore <code>OAuth2Request</code> <code>body</code> parameter. :issue:<code>781</code> :pr:<code>791</code></li> <li>Allow <code>127.0.0.1</code> in insecure transport mode. :pr:<code>788</code></li> <li>Raise <code>MissingCodeException</code> when the <code>code</code> parameter is missing. :issue:<code>793</code> :pr:<code>794</code></li> <li>Fix <code>id_token</code> generation with <code>EdDSA</code> algs. :issue:<code>799</code> :pr:<code>800</code></li> </ul> <h2>Version 1.6.1</h2> <p><strong>Released on Jul 20, 2025</strong></p> <ul> <li>Filter key set with additional "alg" and "use" parameters.</li> <li>Restore and deprecate <code>OAuth2Request</code> <code>body</code> parameter. :issue:<code>781</code></li> </ul> <h2>Version 1.6.0</h2> <p><strong>Released on May 22, 2025</strong></p> <ul> <li>Fix issue when :rfc:<code>RFC9207 <9207></code> is enabled and the authorization endpoint response is not a redirection. :pr:<code>733</code></li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="9ec42561cd"><code>9ec4256</code></a> chore: release 1.6.5</li> <li><a href="b62b5b2757"><code>b62b5b2</code></a> Merge branch 'fix-GHSA-pq5p-34cr-23v9'</li> <li><a href="e0863d5129"><code>e0863d5</code></a> Merge pull request <a href="https://redirect.github.com/authlib/authlib/issues/830">#830</a> from authlib/fix-GHSA-g7f3-828f-7h7m</li> <li><a href="867e3f87b0"><code>867e3f8</code></a> fix(jose): add size limitation to prevent DoS</li> <li><a href="75ad6d4d62"><code>75ad6d4</code></a> Merge pull request <a href="https://redirect.github.com/authlib/authlib/issues/828">#828</a> from authlib/dependabot/github_actions/dot-github/wor...</li> <li><a href="68b982352d"><code>68b9823</code></a> chore(deps): bump SonarSource/sonarqube-scan-action</li> <li><a href="5bdfc4bfff"><code>5bdfc4b</code></a> Merge pull request <a href="https://redirect.github.com/authlib/authlib/issues/827">#827</a> from lisongmin/support-list-params-in-prepare-grant-uri</li> <li><a href="30ea3c5f85"><code>30ea3c5</code></a> feat: support list params in prepare_grant_uri</li> <li><a href="4b5b570339"><code>4b5b570</code></a> fix(jose): add max size for JWE zip=DEF decompression</li> <li><a href="6e35a02ecf"><code>6e35a02</code></a> Merge pull request <a href="https://redirect.github.com/authlib/authlib/issues/825">#825</a> from azmeuk/request-params</li> <li>Additional commits viewable in <a href="https://github.com/authlib/authlib/compare/v1.3.0...v1.6.5">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/ArcadeAI/arcade-mcp/network/alerts). </details> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Skip GHA secret-backed toolkit tests for dependabot, add Dockerized Postgres test setup and default to postgres user, and bump authlib to 1.6.5. > > - **CI**: > - Update `Test stand-alone toolkits (with secrets)` condition to also exclude `github.actor == 'dependabot[bot]'`. > - Execute optional `tests/test_setup.sh` before pytest when present. > - **Postgres toolkit tests**: > - Default `POSTGRES_DATABASE_CONNECTION_STRING` user changed to `postgres` in `toolkits/postgres/tests/test_postgres.py`. > - Add `toolkits/postgres/tests/test_setup.sh` to spin up a Docker `postgres` and wait until ready. > - **Dependencies**: > - Upgrade `authlib` to `1.6.5` in `pyproject.toml`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f3600e7536a409ecd8e645f473d747b9ba363765. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: evan <evan@arcade.dev>
193 lines
5.5 KiB
Python
193 lines
5.5 KiB
Python
import os
|
|
from os import environ
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
from arcade_postgres.tools.postgres import (
|
|
DatabaseEngine,
|
|
discover_schemas,
|
|
discover_tables,
|
|
execute_select_query,
|
|
get_table_schema,
|
|
)
|
|
from arcade_tdk import ToolContext, ToolSecretItem
|
|
from arcade_tdk.errors import RetryableToolError
|
|
from sqlalchemy import text
|
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
|
|
POSTGRES_DATABASE_CONNECTION_STRING = (
|
|
environ.get("TEST_POSTGRES_DATABASE_CONNECTION_STRING")
|
|
or "postgresql://postgres@localhost:5432/postgres"
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_context():
|
|
context = ToolContext()
|
|
context.secrets = []
|
|
context.secrets.append(
|
|
ToolSecretItem(
|
|
key="POSTGRES_DATABASE_CONNECTION_STRING", value=POSTGRES_DATABASE_CONNECTION_STRING
|
|
)
|
|
)
|
|
|
|
return context
|
|
|
|
|
|
# before the tests, restore the database from the dump
|
|
@pytest_asyncio.fixture(autouse=True)
|
|
async def restore_database():
|
|
with open(f"{os.path.dirname(__file__)}/dump.sql") as f:
|
|
engine = create_async_engine(
|
|
POSTGRES_DATABASE_CONNECTION_STRING.replace("postgresql", "postgresql+asyncpg").split(
|
|
"?"
|
|
)[0]
|
|
)
|
|
async with engine.connect() as c:
|
|
queries = f.read().split(";")
|
|
await c.execute(text("BEGIN"))
|
|
for query in queries:
|
|
if query.strip():
|
|
await c.execute(text(query))
|
|
await c.commit()
|
|
await engine.dispose()
|
|
|
|
|
|
@pytest_asyncio.fixture(autouse=True)
|
|
async def cleanup_engines():
|
|
"""Clean up database engines after each test to prevent connection leaks."""
|
|
yield
|
|
# Clean up all cached engines after each test
|
|
await DatabaseEngine.cleanup()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_discover_schemas(mock_context) -> None:
|
|
assert await discover_schemas(mock_context) == ["public"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_discover_tables(mock_context) -> None:
|
|
assert await discover_tables(mock_context) == ["messages", "users"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_table_schema(mock_context) -> None:
|
|
assert await get_table_schema(mock_context, "public", "users") == [
|
|
"id: int (PRIMARY KEY)",
|
|
"name: str (INDEXED)",
|
|
"email: str (INDEXED)",
|
|
"password_hash: str",
|
|
"created_at: datetime",
|
|
"updated_at: datetime",
|
|
"status: str",
|
|
]
|
|
|
|
assert await get_table_schema(mock_context, "public", "messages") == [
|
|
"id: int (PRIMARY KEY)",
|
|
"body: str",
|
|
"user_id: int",
|
|
"created_at: datetime",
|
|
"updated_at: datetime",
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_select_query(mock_context) -> None:
|
|
assert await execute_select_query(
|
|
mock_context,
|
|
select_clause="id, name, email",
|
|
from_clause="users",
|
|
where_clause="id = 1",
|
|
) == [
|
|
"(1, 'Alice', 'alice@example.com')",
|
|
]
|
|
assert await execute_select_query(
|
|
mock_context,
|
|
select_clause="id, name, email",
|
|
from_clause="users",
|
|
order_by_clause="id",
|
|
limit=1,
|
|
offset=1,
|
|
) == [
|
|
"(2, 'Bob', 'bob@example.com')",
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_select_query_with_keywords(mock_context) -> None:
|
|
assert await execute_select_query(
|
|
mock_context,
|
|
select_clause="SELECT id, name, email",
|
|
from_clause="FROM users",
|
|
limit=1,
|
|
) == [
|
|
"(1, 'Alice', 'alice@example.com')",
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_select_query_with_join(mock_context) -> None:
|
|
assert await execute_select_query(
|
|
mock_context,
|
|
select_clause="u.id, u.name, u.email, m.id, m.body",
|
|
from_clause="users u",
|
|
join_clause="messages m ON u.id = m.user_id",
|
|
limit=1,
|
|
) == [
|
|
"(1, 'Alice', 'alice@example.com', 1, 'Hello everyone!')",
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_select_query_with_group_by(mock_context) -> None:
|
|
assert await execute_select_query(
|
|
mock_context,
|
|
select_clause="u.name, COUNT(m.id) AS message_count",
|
|
from_clause="messages m",
|
|
join_clause="users u ON m.user_id = u.id",
|
|
group_by_clause="u.name",
|
|
order_by_clause="message_count DESC",
|
|
limit=2,
|
|
) == [
|
|
"('Evan', 13)",
|
|
"('Alice', 3)",
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_select_query_with_no_results(mock_context) -> None:
|
|
# does not raise an error
|
|
assert (
|
|
await execute_select_query(
|
|
mock_context,
|
|
select_clause="id, name, email",
|
|
from_clause="users",
|
|
where_clause="id = 9999999999",
|
|
)
|
|
== []
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_select_query_with_problem(mock_context) -> None:
|
|
# 'foo' is not a valid id
|
|
with pytest.raises(RetryableToolError) as e:
|
|
await execute_select_query(
|
|
mock_context,
|
|
select_clause="*",
|
|
from_clause="users",
|
|
where_clause="id = 'foo'",
|
|
)
|
|
assert "Do not use * in the select clause" in str(e.value)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_select_query_rejects_non_select(mock_context) -> None:
|
|
with pytest.raises(RetryableToolError) as e:
|
|
await execute_select_query(
|
|
mock_context,
|
|
select_clause="INSERT INTO users (name, email, password_hash) VALUES ('Luigi', 'luigi@example.com', 'password')",
|
|
from_clause="users",
|
|
)
|
|
assert "Only SELECT queries are allowed" in str(e.value)
|