Skip to content

Commit b024ce0

Browse files
authored
Merge pull request #11 from zitadel/add-formatting-and-linting
Introduced formatting and linting for the library
2 parents ee3a6ba + 156688a commit b024ce0

32 files changed

+2626
-2512
lines changed

.github/workflows/linting.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Linting
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
permissions:
10+
contents: write
11+
12+
defaults:
13+
run:
14+
working-directory: ./
15+
16+
jobs:
17+
lint-format:
18+
runs-on: ubuntu-latest
19+
name: Reformat Code
20+
21+
steps:
22+
- name: Checkout code
23+
uses: actions/checkout@v4
24+
with:
25+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
26+
fetch-depth: 0
27+
28+
- name: Install poetry
29+
run: |
30+
pipx install poetry
31+
32+
- name: Setup Python
33+
uses: actions/setup-python@v5
34+
with:
35+
python-version-file: 'pyproject.toml'
36+
cache: 'poetry'
37+
38+
- name: Install Dependencies
39+
run: poetry install --no-interaction --sync --all-extras
40+
41+
- name: Run Formatter
42+
run: poetry run ruff format .
43+
44+
- name: Commit Changes
45+
uses: stefanzweifel/git-auto-commit-action@v5
46+
with:
47+
commit_message: 'style: Apply automated code formatting [skip ci]'
48+
commit_options: '--no-verify'
49+
repository: .
50+
commit_user_name: github-actions[bot]
51+
commit_user_email: github-actions[bot]@users.noreply.github.com
52+
commit_author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

poetry.lock

Lines changed: 635 additions & 607 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ types-python-dateutil = ">= 2.8.19.14"
3838
mypy = ">= 1.5"
3939
testcontainers = "3.7.1"
4040
python-dotenv = "1.0.1"
41+
ruff = "^0.11.5"
4142

4243
[build-system]
4344
requires = ["poetry-core>=1.0.0"]
@@ -89,3 +90,17 @@ disable_error_code = ["import-untyped"]
8990

9091
[tool.flake8]
9192
max-line-length = 99
93+
94+
[tool.ruff]
95+
line-length = 130
96+
fix = true
97+
target-version = "py39"
98+
exclude = [
99+
"zitadel_client/api/*",
100+
"zitadel_client/models/*"
101+
]
102+
103+
[tool.ruff.lint]
104+
select = ["E", "F", "I", "B", "C", "N", "Q", "S", "T"]
105+
extend-select = ["A"]
106+
ignore = ["S101"] # Ignore assert statements

spec/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
@pytest.fixture(scope="session", autouse=True)
66
def load_env() -> None:
7-
"""Load the .env file for the entire test session."""
8-
load_dotenv()
7+
"""Load the .env file for the entire test session."""
8+
load_dotenv()

spec/sdk_test_using_client_credentials_authentication_spec.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import pytest
55

66
import zitadel_client as zitadel
7-
from zitadel_client.auth.client_credentials_authenticator import ClientCredentialsAuthenticator
7+
from zitadel_client.auth.client_credentials_authenticator import (
8+
ClientCredentialsAuthenticator,
9+
)
810

911

1012
@pytest.fixture
@@ -33,42 +35,35 @@ def user_id(client_id: str, client_secret: str, base_url: str) -> str | None:
3335
response = client.users.add_human_user(
3436
body=zitadel.models.V2AddHumanUserRequest(
3537
username=uuid.uuid4().hex,
36-
profile=zitadel.models.V2SetHumanProfile(given_name="John", family_name="Doe"), # type: ignore[call-arg]
37-
email=zitadel.models.V2SetHumanEmail(email=f"johndoe{uuid.uuid4().hex}@caos.ag")
38+
profile=zitadel.models.V2SetHumanProfile(given_name="John", family_name="Doe"), # type: ignore[call-arg]
39+
email=zitadel.models.V2SetHumanEmail(email=f"johndoe{uuid.uuid4().hex}@caos.ag"),
3840
)
3941
)
40-
print("User created:", response)
4142
return response.user_id
4243
except Exception as e:
4344
pytest.fail(f"Exception while creating user: {e}")
4445

4546

46-
def test_should_deactivate_and_reactivate_user_with_valid_token(user_id: str, client_id: str, client_secret: str, base_url: str) -> None:
47+
def test_should_deactivate_and_reactivate_user_with_valid_token(
48+
user_id: str, client_id: str, client_secret: str, base_url: str
49+
) -> None:
4750
"""Test to (de)activate the user with a valid token."""
4851
with zitadel.Zitadel(ClientCredentialsAuthenticator.builder(base_url, client_id, client_secret).build()) as client:
4952
try:
5053
deactivate_response = client.users.deactivate_user(user_id=user_id)
51-
print("User deactivated:", deactivate_response)
54+
assert deactivate_response is not None, "Deactivation response is None"
5255

5356
reactivate_response = client.users.reactivate_user(user_id=user_id)
54-
print("User reactivated:", reactivate_response)
55-
# Adjust based on actual response format
56-
# assert reactivate_response["status"] == "success"
57+
assert reactivate_response is not None, "Reactivation response is None"
5758
except Exception as e:
5859
pytest.fail(f"Exception when calling deactivate_user or reactivate_user with valid token: {e}")
5960

6061

6162
def test_should_not_deactivate_or_reactivate_user_with_invalid_token(user_id: str, base_url: str) -> None:
6263
"""Test to attempt (de)activating the user with an invalid token."""
6364
with zitadel.Zitadel(ClientCredentialsAuthenticator.builder(base_url, "id", "secret").build()) as client:
64-
try:
65+
with pytest.raises(Exception, match="Failed to refresh token: invalid_client: client not found"):
6566
client.users.deactivate_user(user_id=user_id)
66-
pytest.fail("Expected exception when deactivating user with invalid token, but got response.")
67-
except Exception as e:
68-
print("Caught expected UnauthorizedException:", e)
6967

70-
try:
68+
with pytest.raises(Exception, match="Failed to refresh token: invalid_client: client not found"):
7169
client.users.reactivate_user(user_id=user_id)
72-
pytest.fail("Expected exception when reactivating user with invalid token, but got response.")
73-
except Exception as e:
74-
print("Caught expected UnauthorizedException:", e)

spec/sdk_test_using_personal_access_token_authentication_spec.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import pytest
55

66
import zitadel_client as zitadel
7-
from zitadel_client.auth.personal_access_token_authenticator import PersonalAccessTokenAuthenticator
7+
from zitadel_client.auth.personal_access_token_authenticator import (
8+
PersonalAccessTokenAuthenticator,
9+
)
810
from zitadel_client.exceptions import UnauthorizedException
911

1012

@@ -34,11 +36,10 @@ def user_id(valid_token: str, base_url: str) -> str | None:
3436
response = client.users.add_human_user(
3537
body=zitadel.models.V2AddHumanUserRequest(
3638
username=uuid.uuid4().hex,
37-
profile=zitadel.models.V2SetHumanProfile(given_name="John", family_name="Doe"), # type: ignore[call-arg]
38-
email=zitadel.models.V2SetHumanEmail(email=f"johndoe{uuid.uuid4().hex}@caos.ag")
39+
profile=zitadel.models.V2SetHumanProfile(given_name="John", family_name="Doe"), # type: ignore[call-arg]
40+
email=zitadel.models.V2SetHumanEmail(email=f"johndoe{uuid.uuid4().hex}@caos.ag"),
3941
)
4042
)
41-
print("User created:", response)
4243
return response.user_id
4344
except Exception as e:
4445
pytest.fail(f"Exception while creating user: {e}")
@@ -49,31 +50,19 @@ def test_should_deactivate_and_reactivate_user_with_valid_token(user_id: str, va
4950
with zitadel.Zitadel(PersonalAccessTokenAuthenticator(base_url, valid_token)) as client:
5051
try:
5152
deactivate_response = client.users.deactivate_user(user_id=user_id)
52-
print("User deactivated:", deactivate_response)
53+
assert deactivate_response is not None, "Deactivation response is None"
5354

5455
reactivate_response = client.users.reactivate_user(user_id=user_id)
55-
print("User reactivated:", reactivate_response)
56-
# Adjust based on actual response format
57-
# assert reactivate_response["status"] == "success"
56+
assert reactivate_response is not None, "Reactivation response is None"
5857
except Exception as e:
5958
pytest.fail(f"Exception when calling deactivate_user or reactivate_user with valid token: {e}")
6059

6160

6261
def test_should_not_deactivate_or_reactivate_user_with_invalid_token(user_id: str, invalid_token: str, base_url: str) -> None:
6362
"""Test to attempt (de)activating the user with an invalid token."""
6463
with zitadel.Zitadel(PersonalAccessTokenAuthenticator(base_url, invalid_token)) as client:
65-
try:
64+
with pytest.raises(UnauthorizedException):
6665
client.users.deactivate_user(user_id=user_id)
67-
pytest.fail("Expected exception when deactivating user with invalid token, but got response.")
68-
except UnauthorizedException as e:
69-
print("Caught expected UnauthorizedException:", e)
70-
except Exception as e:
71-
pytest.fail(f"Invalid exception when calling the function: {e}")
7266

73-
try:
67+
with pytest.raises(UnauthorizedException):
7468
client.users.reactivate_user(user_id=user_id)
75-
pytest.fail("Expected exception when reactivating user with invalid token, but got response.")
76-
except UnauthorizedException as e:
77-
print("Caught expected UnauthorizedException:", e)
78-
except Exception as e:
79-
pytest.fail(f"Invalid exception when calling the function: {e}")

spec/sdk_test_using_web_token_authentication_spec.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@ def user_id(key_file: str, base_url: str) -> str | None:
3232
response = client.users.add_human_user(
3333
body=zitadel.models.V2AddHumanUserRequest(
3434
username=uuid.uuid4().hex,
35-
profile=zitadel.models.V2SetHumanProfile(given_name="John", family_name="Doe"), # type: ignore[call-arg]
36-
email=zitadel.models.V2SetHumanEmail(email=f"johndoe{uuid.uuid4().hex}@caos.ag")
35+
profile=zitadel.models.V2SetHumanProfile(given_name="John", family_name="Doe"), # type: ignore[call-arg]
36+
email=zitadel.models.V2SetHumanEmail(email=f"johndoe{uuid.uuid4().hex}@caos.ag"),
3737
)
3838
)
39-
print("User created:", response)
4039
return response.user_id
4140
except Exception as e:
4241
pytest.fail(f"Exception while creating user: {e}")
@@ -47,11 +46,9 @@ def test_should_deactivate_and_reactivate_user_with_valid_token(user_id: str, ke
4746
with zitadel.Zitadel(WebTokenAuthenticator.from_json(base_url, key_file)) as client:
4847
try:
4948
deactivate_response = client.users.deactivate_user(user_id=user_id)
50-
print("User deactivated:", deactivate_response)
49+
assert deactivate_response is not None, "Deactivation response is None"
5150

5251
reactivate_response = client.users.reactivate_user(user_id=user_id)
53-
print("User reactivated:", reactivate_response)
54-
# Adjust based on actual response format
55-
# assert reactivate_response["status"] == "success"
52+
assert reactivate_response is not None, "Reactivation response is None"
5653
except Exception as e:
5754
pytest.fail(f"Exception when calling deactivate_user or reactivate_user with valid token: {e}")

test/auth/test_client_credentials_authenticator.py

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,42 @@
22
from datetime import datetime, timezone
33

44
from test.auth.test_oauth_authenticator import OAuthAuthenticatorTest
5-
from zitadel_client.auth.client_credentials_authenticator import ClientCredentialsAuthenticator
5+
from zitadel_client.auth.client_credentials_authenticator import (
6+
ClientCredentialsAuthenticator,
7+
)
68

79

810
class ClientCredentialsAuthenticatorTest(OAuthAuthenticatorTest):
9-
"""
10-
Test for ClientCredentialsAuthenticator to verify token refresh functionality.
11-
Extends the base OAuthAuthenticatorTest class.
12-
"""
11+
"""
12+
Test for ClientCredentialsAuthenticator to verify token refresh functionality.
13+
Extends the base OAuthAuthenticatorTest class.
14+
"""
1315

14-
def test_refresh_token(self) -> None:
15-
time.sleep(20)
16+
def test_refresh_token(self) -> None:
17+
time.sleep(20)
1618

17-
assert self.oauth_host is not None
18-
authenticator = ClientCredentialsAuthenticator.builder(self.oauth_host, "dummy-client", "dummy-secret") \
19-
.scopes("openid", "foo") \
20-
.build()
19+
assert self.oauth_host is not None
20+
authenticator = (
21+
ClientCredentialsAuthenticator.builder(self.oauth_host, "dummy-client", "dummy-secret")
22+
.scopes("openid", "foo")
23+
.build()
24+
)
2125

22-
self.assertTrue(authenticator.get_auth_token(), "Access token should not be empty")
23-
token = authenticator.refresh_token()
24-
self.assertEqual({"Authorization": "Bearer " + token.access_token}, authenticator.get_auth_headers())
25-
self.assertTrue(token.access_token, "Access token should not be null")
26-
self.assertTrue(token.expires_at > datetime.now(timezone.utc), "Token expiry should be in the future")
27-
self.assertEqual(token.access_token, authenticator.get_auth_token())
28-
self.assertEqual(self.oauth_host, authenticator.get_host())
29-
self.assertNotEqual(authenticator.refresh_token().access_token, authenticator.refresh_token().access_token,
30-
"Two refreshToken calls should produce different tokens")
26+
self.assertTrue(authenticator.get_auth_token(), "Access token should not be empty")
27+
token = authenticator.refresh_token()
28+
self.assertEqual(
29+
{"Authorization": "Bearer " + token.access_token},
30+
authenticator.get_auth_headers(),
31+
)
32+
self.assertTrue(token.access_token, "Access token should not be null")
33+
self.assertTrue(
34+
token.expires_at > datetime.now(timezone.utc),
35+
"Token expiry should be in the future",
36+
)
37+
self.assertEqual(token.access_token, authenticator.get_auth_token())
38+
self.assertEqual(self.oauth_host, authenticator.get_host())
39+
self.assertNotEqual(
40+
authenticator.refresh_token().access_token,
41+
authenticator.refresh_token().access_token,
42+
"Two refreshToken calls should produce different tokens",
43+
)

test/auth/test_no_auth_authenticator.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55

66
class NoAuthAuthenticatorTest(unittest.TestCase):
7-
def test_returns_empty_headers_and_default_host(self) -> None:
8-
auth = NoAuthAuthenticator()
9-
self.assertEqual({}, auth.get_auth_headers())
10-
self.assertEqual("http://localhost", auth.get_host())
11-
12-
def test_returns_empty_headers_and_custom_host(self) -> None:
13-
auth = NoAuthAuthenticator("https://custom-host")
14-
self.assertEqual({}, auth.get_auth_headers())
15-
self.assertEqual("https://custom-host", auth.get_host())
7+
def test_returns_empty_headers_and_default_host(self) -> None:
8+
auth = NoAuthAuthenticator()
9+
self.assertEqual({}, auth.get_auth_headers())
10+
self.assertEqual("http://localhost", auth.get_host())
11+
12+
def test_returns_empty_headers_and_custom_host(self) -> None:
13+
auth = NoAuthAuthenticator("https://custom-host")
14+
self.assertEqual({}, auth.get_auth_headers())
15+
self.assertEqual("https://custom-host", auth.get_host())

test/auth/test_oauth_authenticator.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,29 @@
44

55

66
class OAuthAuthenticatorTest(unittest.TestCase):
7-
"""
8-
Base test class for OAuth authenticators.
9-
10-
This class starts a Docker container running the mock OAuth2 server
11-
(ghcr.io/navikt/mock-oauth2-server:2.1.10) before any tests run and stops it after all tests.
12-
It sets the class variable `oauth_host` to the container’s accessible URL.
13-
14-
The container is configured to wait for an HTTP response from the "/" endpoint
15-
with a status code of 405, using HttpWaitStrategy.
16-
"""
17-
oauth_host: str | None = None
18-
mock_oauth2_server: DockerContainer = None
19-
20-
@classmethod
21-
def setUpClass(cls) -> None:
22-
cls.mock_oauth2_server = DockerContainer("ghcr.io/navikt/mock-oauth2-server:2.1.10") \
23-
.with_exposed_ports(8080)
24-
cls.mock_oauth2_server.start()
25-
host = cls.mock_oauth2_server.get_container_host_ip()
26-
port = cls.mock_oauth2_server.get_exposed_port(8080)
27-
cls.oauth_host = f"http://{host}:{port}"
28-
29-
@classmethod
30-
def tearDownClass(cls) -> None:
31-
if cls.mock_oauth2_server is not None:
32-
cls.mock_oauth2_server.stop()
7+
"""
8+
Base test class for OAuth authenticators.
9+
10+
This class starts a Docker container running the mock OAuth2 server
11+
(ghcr.io/navikt/mock-oauth2-server:2.1.10) before any tests run and stops it after all tests.
12+
It sets the class variable `oauth_host` to the container’s accessible URL.
13+
14+
The container is configured to wait for an HTTP response from the "/" endpoint
15+
with a status code of 405, using HttpWaitStrategy.
16+
"""
17+
18+
oauth_host: str | None = None
19+
mock_oauth2_server: DockerContainer = None
20+
21+
@classmethod
22+
def setUpClass(cls) -> None:
23+
cls.mock_oauth2_server = DockerContainer("ghcr.io/navikt/mock-oauth2-server:2.1.10").with_exposed_ports(8080)
24+
cls.mock_oauth2_server.start()
25+
host = cls.mock_oauth2_server.get_container_host_ip()
26+
port = cls.mock_oauth2_server.get_exposed_port(8080)
27+
cls.oauth_host = f"http://{host}:{port}"
28+
29+
@classmethod
30+
def tearDownClass(cls) -> None:
31+
if cls.mock_oauth2_server is not None:
32+
cls.mock_oauth2_server.stop()

0 commit comments

Comments
 (0)