• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ansible / awx-plugins / 21957430481

12 Feb 2026 05:35PM UTC coverage: 71.724% (-0.07%) from 71.794%
21957430481

push

github

web-flow
Merge pull request #155 from webknjaz/maintenance/pylint-preferred-modules

This patch switches from importing `unittest.mock` to using `pytest-mock` — a native integration of mocking and spying on invocation that is meant to be used in `pytest` ecosystem instead of stdlib directly.

It also fixes up the `pylint` configuration, re-enabling the `preferred-modules` pylint rule, which required moving `awx` and `django` modules to the `deprecated-modules` setting due to `preferred-modules` getting triggered on indirect imports in external projects.

And the last touch is enabling a similar rule in Ruff that is able to show nicer recommendations to contributors tripping over this.

37 of 37 branches covered (100.0%)

Branch coverage included in aggregate %.

19 of 37 new or added lines in 2 files covered. (51.35%)

1 existing line in 1 file now uncovered.

2289 of 3206 relevant lines covered (71.4%)

2.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.97
/tests/credential_plugins_test.py
1
import pytest
3✔
2
from pytest_mock import MockerFixture
3✔
3

4
import requests
3✔
5

6
from awx_plugins.credentials import aim, hashivault
3✔
7

8

9
def test_imported_azure_cloud_sdk_vars() -> None:
3✔
10
    from awx_plugins.credentials import azure_kv
3✔
11

12
    assert len(azure_kv.clouds) > 0
3✔
13
    assert all([hasattr(c, 'name') for c in azure_kv.clouds])
3✔
14
    assert all([hasattr(c, 'suffixes') for c in azure_kv.clouds])
3✔
15
    assert all([hasattr(c.suffixes, 'keyvault_dns') for c in azure_kv.clouds])
3✔
16

17

18
def test_hashivault_approle_auth() -> None:
3✔
19
    kwargs = {
3✔
20
        'role_id': 'the_role_id',
3✔
21
        'secret_id': 'the_secret_id',
3✔
22
    }
23
    expected_res = {
3✔
24
        'role_id': 'the_role_id',
3✔
25
        'secret_id': 'the_secret_id',
3✔
26
    }
27
    res = hashivault.approle_auth(**kwargs)  # type: ignore[no-untyped-call]
×
28
    assert res == expected_res
×
29

30

31
def test_hashivault_kubernetes_auth(mocker: MockerFixture) -> None:
3✔
32
    kwargs = {
3✔
33
        'kubernetes_role': 'the_kubernetes_role',
3✔
34
    }
35
    expected_res = {
3✔
36
        'role': 'the_kubernetes_role',
3✔
37
        'jwt': 'the_jwt',
3✔
38
    }
39
    path_mock = mocker.patch('pathlib.Path')
3✔
NEW
40
    path_mock.return_value.open = mocker.mock_open(read_data='the_jwt')
×
NEW
41
    res = hashivault.kubernetes_auth(  # type: ignore[no-untyped-call]
×
42
        **kwargs,
3✔
43
    )
NEW
44
    path_mock.assert_called_with(
×
45
        '/var/run/secrets/kubernetes.io/serviceaccount/token',
3✔
46
    )
NEW
47
    assert res == expected_res
×
48

49

50
def test_hashivault_client_cert_auth_explicit_role() -> None:
3✔
51
    kwargs = {
3✔
52
        'client_cert_role': 'test-cert-1',
3✔
53
    }
54
    expected_res = {
3✔
55
        'name': 'test-cert-1',
3✔
56
    }
57
    res = hashivault.client_cert_auth(  # type: ignore[no-untyped-call]
×
58
        **kwargs,
3✔
59
    )
60
    assert res == expected_res
×
61

62

63
def test_hashivault_client_cert_auth_no_role() -> None:
3✔
64
    kwargs: dict[str, str] = {}
3✔
65
    expected_res = {
3✔
66
        'name': None,
3✔
67
    }
68
    res = hashivault.client_cert_auth(  # type: ignore[no-untyped-call]
×
69
        **kwargs,
3✔
70
    )
71
    assert res == expected_res
×
72

73

74
def test_hashivault_userpass_auth() -> None:
3✔
75
    kwargs = {'username': 'the_username', 'password': 'the_password'}
3✔
76
    expected_res = {'username': 'the_username', 'password': 'the_password'}
3✔
77
    res = hashivault.userpass_auth(**kwargs)  # type: ignore[no-untyped-call]
×
78
    assert res == expected_res
×
79

80

81
def test_hashivault_handle_auth_token() -> None:
3✔
82
    kwargs = {
3✔
83
        'token': 'the_token',
3✔
84
    }
85
    token = hashivault.handle_auth(**kwargs)  # type: ignore[no-untyped-call]
×
86
    assert token == kwargs['token']
×
87

88

89
def test_hashivault_handle_auth_approle(mocker: MockerFixture) -> None:
3✔
90
    kwargs = {
3✔
91
        'role_id': 'the_role_id',
3✔
92
        'secret_id': 'the_secret_id',
3✔
93
    }
94
    method_mock = mocker.patch.object(hashivault, 'method_auth')
3✔
NEW
95
    method_mock.return_value = 'the_token'
×
NEW
96
    token = hashivault.handle_auth(  # type: ignore[no-untyped-call]
×
97
        **kwargs,
3✔
98
    )
NEW
99
    method_mock.assert_called_with(**kwargs, auth_param=kwargs)
×
NEW
100
    assert token == 'the_token'
×
101

102

103
def test_hashivault_handle_auth_kubernetes(mocker: MockerFixture) -> None:
3✔
104
    kwargs = {
3✔
105
        'kubernetes_role': 'the_kubernetes_role',
3✔
106
    }
107
    method_mock = mocker.patch.object(hashivault, 'method_auth')
3✔
108
    path_mock = mocker.patch('pathlib.Path')
3✔
NEW
109
    path_mock.return_value.open = mocker.mock_open(read_data='the_jwt')
×
NEW
110
    method_mock.return_value = 'the_token'
×
NEW
111
    token = hashivault.handle_auth(  # type: ignore[no-untyped-call]
×
112
        **kwargs,
3✔
113
    )
NEW
114
    method_mock.assert_called_with(
×
115
        **kwargs,
3✔
116
        auth_param={
117
            'role': 'the_kubernetes_role',
3✔
118
            'jwt': 'the_jwt',
3✔
119
        },
120
    )
NEW
121
    assert token == 'the_token'
×
122

123

124
def test_hashivault_handle_auth_client_cert(mocker: MockerFixture) -> None:
3✔
125
    kwargs = {
3✔
126
        'client_cert_public': 'foo',
3✔
127
        'client_cert_private': 'bar',
3✔
128
        'client_cert_role': 'test-cert-1',
3✔
129
    }
130
    auth_params = {
3✔
131
        'name': 'test-cert-1',
3✔
132
    }
133
    method_mock = mocker.patch.object(hashivault, 'method_auth')
3✔
NEW
134
    method_mock.return_value = 'the_token'
×
NEW
135
    token = hashivault.handle_auth(  # type: ignore[no-untyped-call]
×
136
        **kwargs,
3✔
137
    )
NEW
138
    method_mock.assert_called_with(**kwargs, auth_param=auth_params)
×
NEW
139
    assert token == 'the_token'
×
140

141

142
def test_hashivault_handle_auth_not_enough_args() -> None:
3✔
143
    with pytest.raises(Exception):
3✔
144
        hashivault.handle_auth()  # type: ignore[no-untyped-call]
×
145

146

147
class TestDelineaImports:
3✔
148
    """These module have a try-except for ImportError which will allow using
149
    the older library but we do not want the awx_devel image to have the older
150
    library, so these tests are designed to fail if these wind up using the
151
    fallback import."""
152

153
    def test_dsv_import(self) -> None:
3✔
154
        from awx_plugins.credentials.dsv import SecretsVault  # noqa: F401
3✔
155

156
        # assert this module as opposed to older thycotic.secrets.vault
157
        assert SecretsVault.__module__ == 'delinea.secrets.vault'
×
158

159
    def test_tss_import(self) -> None:
3✔
160
        from awx_plugins.credentials.tss import (  # noqa: F401
3✔
161
            DomainPasswordGrantAuthorizer,
162
            PasswordGrantAuthorizer,
163
            SecretServer,
164
            ServerSecret,
165
        )
166

167
        for cls in (
×
168
            DomainPasswordGrantAuthorizer,
×
169
            PasswordGrantAuthorizer,
×
170
            SecretServer,
×
171
            ServerSecret,
×
172
        ):
173
            # assert this module as opposed to older thycotic.secrets.server
174
            assert cls.__module__ == 'delinea.secrets.server'
×
175

176

177
@pytest.mark.parametrize(
3✔
178
    (
179
        'reason',
3✔
180
        'expected_url_in_exc',
3✔
181
        'expected_response_url_literal',
3✔
182
    ),
183
    (
184
        pytest.param(
3✔
185
            'foobar123',
3✔
186
            r'.*http://testurl\.com/AIMWebService/api/Accounts\?'
3✔
187
            r'AppId=\*\*\*\*&Query=\*\*\*\*&QueryFormat=test&'
188
            r'reason=\*\*\*\*.*',
189
            'http://testurl.com/AIMWebService/api/Accounts?'
3✔
190
            'AppId=****&Query=****&QueryFormat=test&reason=****',
191
            id='with-reason',
3✔
192
        ),
193
        pytest.param(
3✔
194
            '',
3✔
195
            r'.*http://testurl\.com/AIMWebService/api/Accounts\?'
3✔
196
            r'AppId=\*\*\*\*&Query=\*\*\*\*&QueryFormat=test.*',
197
            'http://testurl.com/AIMWebService/api/Accounts?'
3✔
198
            'AppId=****&Query=****&QueryFormat=test',
199
            id='no-reason',
3✔
200
        ),
201
    ),
202
)
203
def test_aim_sensitive_traceback_masked(
3✔
204
    mocker: MockerFixture,
205
    monkeypatch: pytest.MonkeyPatch,
206
    reason: str,
207
    expected_url_in_exc: str,
208
    expected_response_url_literal: str,
209
) -> None:
210
    """Ensure that the sensitive information is not leaked in the traceback."""
211
    my_response = requests.Response()
3✔
212
    my_response.status_code = 404
3✔
213
    my_response.url = 'not_found'
3✔
214

215
    aim_request_mock = mocker.Mock(
×
216
        autospec=True,
3✔
217
        name='aim_request',
3✔
218
        return_value=my_response,
3✔
219
    )
220
    monkeypatch.setattr(aim.requests, 'get', aim_request_mock)
×
221

222
    with pytest.raises(
3✔
223
        requests.exceptions.HTTPError,
3✔
224
        match=expected_url_in_exc,
3✔
225
    ) as e:
3✔
226
        aim.aim_backend(  # type: ignore[no-untyped-call]
×
227
            url='http://testurl.com',
3✔
228
            app_id='foobar123',
3✔
229
            object_query='foobar123',
3✔
230
            object_query_format='test',
3✔
231
            reason=reason,
3✔
232
            verify=True,
3✔
233
        )
234

235
    assert e.value.response.url == expected_response_url_literal
3✔
236
    assert 'foobar123' not in str(e)
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc