Skip to content

Commit 20bba9d

Browse files
authored
Merge pull request #85 from dnplus/codex/add-github-action-for-pypi-deployment
Enhance OAuth component tests with mocks
2 parents 96c4833 + c29ab1a commit 20bba9d

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed

.github/workflows/pypi-publish.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Publish Python Package
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
jobs:
9+
build-and-publish:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v3
13+
- name: Set up Python
14+
uses: actions/setup-python@v4
15+
with:
16+
python-version: '3.x'
17+
- name: Install dependencies
18+
run: |
19+
python -m pip install --upgrade pip
20+
pip install build pytest
21+
pip install -e .
22+
- name: Run tests
23+
run: pytest
24+
- name: Build package
25+
run: python -m build
26+
- name: Publish package to PyPI
27+
uses: pypa/gh-action-pypi-publish@release/v1
28+
with:
29+
password: ${{ secrets.PYPI_API_TOKEN }}
30+

.github/workflows/tests.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v3
13+
- name: Set up Python
14+
uses: actions/setup-python@v4
15+
with:
16+
python-version: '3.x'
17+
- name: Install dependencies
18+
run: |
19+
python -m pip install --upgrade pip
20+
pip install -e .
21+
pip install pytest
22+
- name: Run tests
23+
run: pytest
24+

tests/test_internal.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import streamlit as st
2+
from streamlit_oauth import _generate_state, _generate_pkce_pair
3+
import pytest
4+
5+
6+
def test_generate_state_same_key():
7+
st.session_state.clear()
8+
s1 = _generate_state(key="a")
9+
s2 = _generate_state(key="a")
10+
assert s1 == s2
11+
12+
13+
def test_generate_state_different_key():
14+
st.session_state.clear()
15+
s1 = _generate_state(key="a")
16+
s2 = _generate_state(key="b")
17+
assert s1 != s2
18+
19+
20+
def test_generate_pkce_pair_same_key():
21+
st.session_state.clear()
22+
p1 = _generate_pkce_pair("S256", key="x")
23+
p2 = _generate_pkce_pair("S256", key="x")
24+
assert p1 == p2
25+
assert len(p1) == 2
26+
27+
28+
def test_generate_pkce_pair_invalid():
29+
st.session_state.clear()
30+
with pytest.raises(Exception):
31+
_generate_pkce_pair("plain", key="x")

tests/test_oauth_component.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import time
2+
import streamlit as st
3+
import pytest
4+
from unittest.mock import AsyncMock
5+
6+
from streamlit_oauth import OAuth2Component, OAuth2, StreamlitOauthError
7+
8+
9+
def test_authorize_button_success(monkeypatch):
10+
st.session_state.clear()
11+
client = OAuth2("id", "secret", "auth", "token")
12+
oauth = OAuth2Component(client=client)
13+
14+
# Mock async client methods
15+
monkeypatch.setattr(oauth.client, "get_authorization_url", AsyncMock(return_value="http://auth"))
16+
monkeypatch.setattr(oauth.client, "get_access_token", AsyncMock(return_value={"access_token": "tok"}))
17+
18+
# Force deterministic state and component output
19+
monkeypatch.setattr("streamlit_oauth._generate_state", lambda key=None: "STATE")
20+
monkeypatch.setattr("streamlit_oauth._authorize_button", lambda **kwargs: {"code": "CODE", "state": "STATE"})
21+
22+
result = oauth.authorize_button("Login", "http://cb", "scope", key="k")
23+
assert result["token"]["access_token"] == "tok"
24+
assert f"state-k" not in st.session_state
25+
26+
27+
def test_authorize_button_state_mismatch(monkeypatch):
28+
st.session_state.clear()
29+
client = OAuth2("id", "secret", "auth", "token")
30+
oauth = OAuth2Component(client=client)
31+
32+
monkeypatch.setattr(oauth.client, "get_authorization_url", AsyncMock(return_value="http://auth"))
33+
monkeypatch.setattr(oauth.client, "get_access_token", AsyncMock(return_value={"access_token": "tok"}))
34+
monkeypatch.setattr("streamlit_oauth._generate_state", lambda key=None: "GOOD")
35+
monkeypatch.setattr("streamlit_oauth._authorize_button", lambda **kwargs: {"code": "CODE", "state": "BAD"})
36+
37+
with pytest.raises(StreamlitOauthError):
38+
oauth.authorize_button("Login", "http://cb", "scope", key="k")
39+
40+
41+
def test_refresh_token_expired(monkeypatch):
42+
client = OAuth2("id", "secret", "auth", "token")
43+
oauth = OAuth2Component(client=client)
44+
45+
monkeypatch.setattr(oauth.client, "refresh_token", AsyncMock(return_value={"access_token": "new"}))
46+
47+
token = {"access_token": "old", "refresh_token": "r", "expires_at": time.time() - 1}
48+
result = oauth.refresh_token(token)
49+
50+
assert result["access_token"] == "new"
51+
52+
53+
def test_revoke_token(monkeypatch):
54+
client = OAuth2("id", "secret", "auth", "token")
55+
oauth = OAuth2Component(client=client)
56+
revoke_mock = AsyncMock()
57+
monkeypatch.setattr(oauth.client, "revoke_token", revoke_mock)
58+
59+
assert oauth.revoke_token({"access_token": "abc"}) is True
60+
revoke_mock.assert_awaited_once()

0 commit comments

Comments
 (0)